├── .github
└── FUNDING.yml
├── README.md
├── app
├── __init__.py
├── __pycache__
│ └── __init__.cpython-310.pyc
├── api
│ ├── __init__.py
│ ├── __pycache__
│ │ └── __init__.cpython-310.pyc
│ └── v2
│ │ ├── __init__.py
│ │ ├── __pycache__
│ │ └── __init__.cpython-310.pyc
│ │ └── endpoints
│ │ ├── __init__.py
│ │ ├── __pycache__
│ │ ├── __init__.cpython-310.pyc
│ │ └── proxy.cpython-310.pyc
│ │ └── proxy.py
├── assets
│ ├── channel.m3u
│ └── config.ini
├── common
│ ├── __init__.py
│ ├── __pycache__
│ │ ├── __init__.cpython-310.pyc
│ │ ├── cache_tools.cpython-310.pyc
│ │ └── request.cpython-310.pyc
│ ├── cache_tools.py
│ └── request.py
├── conf
│ ├── __init__.py
│ ├── __pycache__
│ │ ├── __init__.cpython-310.pyc
│ │ └── config.cpython-310.pyc
│ └── config.py
├── db
│ ├── DBtools.py
│ ├── __init__.py
│ ├── __pycache__
│ │ ├── DBtools.cpython-310.pyc
│ │ ├── __init__.cpython-310.pyc
│ │ ├── dbMysql.cpython-310.pyc
│ │ └── localfile.cpython-310.pyc
│ ├── dbMysql.py
│ └── localfile.py
├── main.py
├── plugins
│ ├── __init__.py
│ ├── __pycache__
│ │ └── __init__.cpython-310.pyc
│ └── proxy
│ │ ├── __init__.py
│ │ ├── __pycache__
│ │ ├── __init__.cpython-310.pyc
│ │ ├── endecrypt.cpython-310.pyc
│ │ ├── tasks.cpython-310.pyc
│ │ ├── tools.cpython-310.pyc
│ │ └── utile.cpython-310.pyc
│ │ ├── endecrypt.py
│ │ ├── tasks.py
│ │ ├── tools.py
│ │ └── utile.py
└── scheams
│ ├── __init__.py
│ ├── __pycache__
│ ├── __init__.cpython-310.pyc
│ ├── api_model.cpython-310.pyc
│ └── response.cpython-310.pyc
│ ├── api_model.py
│ └── response.py
├── img
└── demo.gif
├── main.py
└── requirements.txt
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | custom: ["https://ik.imagekit.io/naihe/pay/hbm.jpg"]
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | [ProxyURL](https://github.com/239144498/ProxyURL)
4 | -------------
5 | [](https://www.python.org/downloads/release/python-380/)
6 | [](https://github.com/239144498/ProxyURL/stargazers)
7 | [](https://github.com/239144498/ProxyURL/blob/main/LICENSE)
8 |
9 |
10 |
11 | - 程序使用教程已发布在`微信公众号【pqhero】回复【使用教程】`,可打开微信访问:[https://0a.fit/obLUQ](https://0a.fit/obLUQ),或者扫码查看
12 |
13 |
14 |
15 |
16 | 程序介绍
17 | ---
18 | > 🚨如需使用私有服务器运行本项目,请参考部署方式[部署教程](./README.md#python%E9%83%A8%E7%BD%B2linux)
19 | >
20 | **ProxyURL**是一个从 [Streaming-Media-Server-Pro](https://github.com/239144498/Streaming-Media-Server-Pro) 项目中抽离出的流媒体代理程序。程序新增缓存功能,多人同时观看效果更流畅。
21 | 理论支持代理任意频道,代理后可解决**频道卡顿**问题、网页播放的**跨域**问题、**封锁地区**问题等等。更多使用方式等你去发现。
22 |
23 | 代理接口演示
24 | ---
25 | _[Click to view](https://ik.imagekit.io/naihe/github/demo.gif)_
26 | 
27 |
28 |
29 | 演示站点: 我很脆弱...请勿压测(·•᷄ࡇ•᷅ )
30 | ---
31 | API Document: https://proxy.naihe.me/docs
32 | 为防止滥用,本接口已经加入了鉴权机制。
33 | **未来将添加代理4k频道接口**
34 |
35 | 程序功能
36 | ---
37 | - 代理任意电视频道的视频流
38 | - 代理缓存播放
39 | - 代理生成m3u文件
40 | - 代理生成m3u8文件
41 | - 异步下载流
42 | - 流媒体转发
43 | - 支持短回放
44 | - 自定义频道列表
45 |
46 | 支持代理的链接格式
47 | ---
48 | > 💡提示:包含但不仅限于以下例子,如果遇到链接解析失败请开启一个新 [issue](https://github.com/239144498/ProxyURL/issues)
49 |
50 | 程序有检测url链接格式功能,一般m3u8链接分为以下两种:
51 |
52 | - 含有域名链接的数据
53 | ```
54 | #EXTM3U
55 | #EXT-X-VERSION:3
56 | #EXT-X-TARGETDURATION:10
57 | #EXT-X-MEDIA-SEQUENCE:1668994593
58 | #EXTINF:10.0,5290508
59 | http://117.169.xxx.xxx:8080/wh7f454c46tw3004853380_1728812826/live/cctv-4/HD-4000k-1080P-cctv4_20230117_205041_205051.ts
60 | #EXTINF:10.0,5306864
61 | http://117.169.xxx.xxx:8080/wh7f454c46tw3004853380_1728812826/live/cctv-4/HD-4000k-1080P-cctv4_20230117_205051_205101.ts
62 | #EXTINF:10.0,5271708
63 | http://117.169.xxx.xxx:8080/wh7f454c46tw3004853380_1728812826/live/cctv-4/HD-4000k-1080P-cctv4_20230117_205101_205111.ts
64 | ...
65 | ...
66 | ```
67 | - 无域名链接的数据
68 |
69 | ```
70 | #EXTM3U
71 | #EXT-X-VERSION:3
72 | #EXT-X-MEDIA-SEQUENCE:167395846
73 | #EXT-X-TARGETDURATION:10
74 | #EXTINF:10.200,
75 | /cntv20210928/cctvwbnd/cctv1_2_md/167395846.ts?region=beijing
76 | #EXTINF:9.800,
77 | /cntv20210928/cctvwbnd/cctv1_2_md/167395847.ts?region=beijing
78 | #EXTINF:10.200,
79 | /cntv20210928/cctvwbnd/cctv1_2_md/167395848.ts?region=beijing
80 | #EXTINF:9.800,
81 | /cntv20210928/cctvwbnd/cctv1_2_md/167395849.ts?region=beijing
82 | ```
83 | 这两种格式已涵盖市面上大部分m3u8链接。
84 |
85 |
86 | Python部署(Linux)
87 | ---
88 | > 💡提示:最好将本项目部署至美国地区的服务器,否则可能会出现奇怪的BUG。
89 |
90 | 推荐大家使用[Digitalocean](https://www.digitalocean.com/?refcode=45e25f5e4569&utm_campaign=Referral_Invite&utm_medium=Referral_Program&utm_source=badge)的服务器,主要是因为**免费**。
91 |
92 |
93 |
94 | 使用我的邀请链接注册,你可以获得$200的credit,当你在上面消费$25时,我也可以获得$25的奖励。
95 |
96 | 我的邀请链接:
97 |
98 | [https://m.do.co/c/45e25f5e4569](https://m.do.co/c/45e25f5e4569)
99 | > 根据以下通用命令部署本项目
100 | ### clone仓库
101 |
102 | python版本>=3.8+
103 |
104 | ``` code
105 | git clone https://github.com/239144498/ProxyURL.git
106 | ```
107 |
108 | ### 安装依赖
109 |
110 | ``` code
111 | pip install -r requirements.txt
112 | ```
113 |
114 | ### 运行
115 |
116 | ``` code
117 | python3 main.py
118 | ```
119 |
120 | 项目使用教程
121 | ---
122 | 关注公众号【pqhero】查看程序使用详情
123 |
124 |
125 |
126 | 支持频道
127 | ---
128 |
129 | - [x] 在channel.m3u文件添加代理频道
130 |
131 | License
132 | ---
133 | [GNU-3.0 © naihe](https://github.com/239144498/ProxyURL/blob/main/LICENSE)
134 |
135 |
--------------------------------------------------------------------------------
/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/__pycache__/__init__.cpython-310.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kfcx/ProxyURL/43d3b6435297f180209b8d4f4fb99ca729e5bb8b/app/__pycache__/__init__.cpython-310.pyc
--------------------------------------------------------------------------------
/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 | from loguru import *
8 | import sys
9 | from fastapi import *
10 | from apscheduler.executors.pool import *
11 | import logging
12 | import base64
13 | from apscheduler.schedulers.background import *
14 | from apscheduler.triggers.cron import *
15 | from app.conf import *
16 | from app.plugins.proxy.tasks import *
17 | from .v2 import *
18 | from ..conf.config import *
19 | from ..scheams.response import *
20 |
21 |
22 | def init_app():
23 | app = FastAPI(
24 | title=config.TITLE,
25 | description=config.DESC,
26 | version=config.VERSION,
27 | debug=DEBUG
28 | )
29 | return app
30 |
31 |
32 | app = init_app()
33 | app.include_router(v2)
34 |
35 |
36 | @app.get('/')
37 | async def Root_Path():
38 | data = {
39 | "API_status": "Running",
40 | "Version": config.VERSION,
41 | "Web_APP": "https://proxy.naihe.me/",
42 | "API_Document": "https://proxy.naihe.me/docs",
43 | "GitHub": "https://github.com/239144498/proxyURL",
44 | }
45 | return JSONResponse(data)
46 |
47 |
48 | @app.on_event("startup")
49 | async def startup():
50 | pass
51 |
52 |
53 | @app.on_event("shutdown")
54 | async def shutdown():
55 | pass
56 |
--------------------------------------------------------------------------------
/app/api/__pycache__/__init__.cpython-310.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kfcx/ProxyURL/43d3b6435297f180209b8d4f4fb99ca729e5bb8b/app/api/__pycache__/__init__.cpython-310.pyc
--------------------------------------------------------------------------------
/app/api/v2/__init__.py:
--------------------------------------------------------------------------------
1 | from fastapi import *
2 | from .endpoints import *
3 |
4 | v2 = APIRouter()
5 |
6 | v2.include_router(tv)
7 |
--------------------------------------------------------------------------------
/app/api/v2/__pycache__/__init__.cpython-310.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kfcx/ProxyURL/43d3b6435297f180209b8d4f4fb99ca729e5bb8b/app/api/v2/__pycache__/__init__.cpython-310.pyc
--------------------------------------------------------------------------------
/app/api/v2/endpoints/__init__.py:
--------------------------------------------------------------------------------
1 | from .proxy import *
2 |
--------------------------------------------------------------------------------
/app/api/v2/endpoints/__pycache__/__init__.cpython-310.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kfcx/ProxyURL/43d3b6435297f180209b8d4f4fb99ca729e5bb8b/app/api/v2/endpoints/__pycache__/__init__.cpython-310.pyc
--------------------------------------------------------------------------------
/app/api/v2/endpoints/__pycache__/proxy.cpython-310.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kfcx/ProxyURL/43d3b6435297f180209b8d4f4fb99ca729e5bb8b/app/api/v2/endpoints/__pycache__/proxy.cpython-310.pyc
--------------------------------------------------------------------------------
/app/api/v2/endpoints/proxy.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2022/10/8
3 | # @Author : Naihe
4 | # @Email : 239144498@qq.com
5 | # @File : proxy.py
6 | # @Software: PyCharm
7 | import asyncio
8 | from base64 import *
9 | from threading import *
10 | from loguru import *
11 | from urllib.parse import *
12 | from fastapi.requests import *
13 | from fastapi import *
14 | from fastapi.background import *
15 | from starlette.responses import *
16 | from app.common.cache_tools import *
17 | from app.plugins.proxy.tools import *
18 | from app.plugins.proxy.utile import *
19 | from app.conf.config import *
20 | from app.scheams.response import *
21 |
22 |
23 | tv = APIRouter(tags=["流媒体代理"])
24 |
25 |
26 | @tv.get('/proxy.m3u8')
27 | async def proxy(request: Request,
28 | url: str = Query(..., regex=config.url_regex)):
29 | """
30 | ## 用途/Usage
31 | - 代理任意链接
32 | """
33 | url = dict(request.query_params)
34 | if url.get("url"):
35 | url = parse(url)
36 | _data = get_m3u8_down(url)
37 | return StreamingResponse(processing(url, iter(_data.split("\n"))), 200, headers=headers2)
38 |
39 |
40 | @tv.get('/file.ts')
41 | async def file(x: str = Query(...)):
42 | """
43 | ## 用途/Usage
44 | - 下载流视频片
45 | """
46 | a = b64decode(x.encode("utf-8")).decode("utf-8")
47 | return Response(content=download(a), status_code=200, headers=headers, media_type='video/MP2T')
48 |
49 |
50 | @tv.get('/program.m3u')
51 | async def program_proxy():
52 | """
53 | ## 用途/Usage
54 | - 代理频道列表
55 | """
56 | with open("app/assets/channel.m3u", "r", encoding="utf-8") as f:
57 | data = f.read()
58 | return Response(content=data, status_code=200)
59 |
60 |
61 | @tv.get('/about')
62 | async def Stream_Proxy_Pro(request: Request):
63 | """
64 | ## 接口文档
65 | """
66 | return RedirectResponse("http://proxy.naihe.me/docs")
--------------------------------------------------------------------------------
/app/assets/channel.m3u:
--------------------------------------------------------------------------------
1 | #EXTM3U
2 | # 代理频道URL示例,请自行替换
3 |
4 | #EXTINF:-1,tvg-id="CCTV1" tvg-name="CCTV1" tvg-logo="https://epg.112114.xyz/logo/CCTV1.png" group-title="央视",CCTV1
5 | /proxy.m3u8?url=https://cctvwbndks.v.kcdnvip.com/cctvwbnd/cctv1_2/index.m3u8?BR=md®ion=shanghai
6 | #EXTINF:-1,卢村远眺
7 | /proxy.m3u8?url=https://gccncc.xxxx.com/gc/yxlcyt_1/index.m3u8?contentid=2820180516001
8 | #EXTINF:-1,九寨沟
9 | /proxy.m3u8?url=https://gctxyc.xxxx.com/gc/wygjt1_1/index.m3u8?contentid=2820180516001
10 | #EXTINF:-1,熊大熊二
11 | /proxy.m3u8?url=https://newcntv.xxxx.com:8080/asp/hls/1200/0303000a/3/default/1733da751de64e6e910abda889d87a26/1200.m3u8
12 |
--------------------------------------------------------------------------------
/app/assets/config.ini:
--------------------------------------------------------------------------------
1 | [default]
2 | defaultdb =
3 | port = 8080
4 | localhost =
5 | vbuffer = 3
6 | active_mode = True
7 |
8 | [advanced]
9 | host1 =
10 | host2 =
11 | tvglogo =
12 | debug = False
13 |
14 | [mysql]
15 | host =
16 | user =
17 | password =
18 | port =
19 | database =
20 |
21 | [redis]
22 | host =
23 | port =
24 | password =
25 |
--------------------------------------------------------------------------------
/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/__pycache__/__init__.cpython-310.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kfcx/ProxyURL/43d3b6435297f180209b8d4f4fb99ca729e5bb8b/app/common/__pycache__/__init__.cpython-310.pyc
--------------------------------------------------------------------------------
/app/common/__pycache__/cache_tools.cpython-310.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kfcx/ProxyURL/43d3b6435297f180209b8d4f4fb99ca729e5bb8b/app/common/__pycache__/cache_tools.cpython-310.pyc
--------------------------------------------------------------------------------
/app/common/__pycache__/request.cpython-310.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kfcx/ProxyURL/43d3b6435297f180209b8d4f4fb99ca729e5bb8b/app/common/__pycache__/request.cpython-310.pyc
--------------------------------------------------------------------------------
/app/common/cache_tools.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # @Time : 2023/1/12
3 | # @Author : Naihe
4 | # @Email : 239144498@qq.com
5 | # @File : cache_tools.py
6 | # @Software: PyCharm
7 | import time
8 | from base64 import b64encode
9 | import aiohttp
10 | import itertools
11 | import requests
12 | from loguru import *
13 | from threading import *
14 | from collections import *
15 | from urllib.parse import *
16 | from app.conf.config import *
17 | from app.plugins.proxy.tools import *
18 |
19 |
20 | async def processing(url, data):
21 | for _temp in data:
22 | if ".ts" in _temp:
23 | if not is_url(_temp):
24 | yield "/file.ts?x=" + b64encode(_temp.encode("utf-8")).decode("utf-8")
25 | else:
26 | yield "/file.ts/?x=" + b64encode(urljoin(url, _temp).encode("utf-8")).decode("utf-8")
27 | else:
28 | yield _temp
29 | yield "\n"
30 |
31 |
32 | def download(url):
33 | if _temp_ts := ts_info.get(url):
34 | return _temp_ts
35 | header = {
36 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:103.0) Gecko/20100101 Firefox/103.0",
37 | }
38 | with requests.get(url=url, headers=header) as res:
39 | _data = res.content
40 | ts_info[url] = _data
41 | return _data
42 |
43 |
44 | def get_m3u8_down(url):
45 | headers = {
46 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:104.0) Gecko/20100101 Firefox/104.0"
47 | }
48 | with requests.get(url=url, headers=headers) as res:
49 | _data = res.text
50 | return _data
51 |
--------------------------------------------------------------------------------
/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 *
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 = requests
22 | self.proxies = None
23 |
24 | def session(self):
25 | return requests.session()
26 |
27 | def get(self, url, headers=None):
28 | return self.request.get(url, headers=headers, proxies=self.proxies)
29 |
30 | def post(self, url, data=None, json=None, headers=None):
31 | return self.request.post(url, data=data, json=json, headers=headers, proxies=self.proxies)
32 |
33 | def put(self, url, data=None, json=None, headers=None):
34 | return self.request.put(url, data=data, json=json, headers=headers, proxies=self.proxies)
35 |
36 | def delete(self, url, data=None, json=None, headers=None):
37 | return self.request.delete(url, data=data, json=json, headers=headers, proxies=self.proxies)
38 |
39 |
40 | request = netreq()
41 |
42 |
--------------------------------------------------------------------------------
/app/conf/__init__.py:
--------------------------------------------------------------------------------
1 | from .config import *
2 |
--------------------------------------------------------------------------------
/app/conf/__pycache__/__init__.cpython-310.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kfcx/ProxyURL/43d3b6435297f180209b8d4f4fb99ca729e5bb8b/app/conf/__pycache__/__init__.cpython-310.pyc
--------------------------------------------------------------------------------
/app/conf/__pycache__/config.cpython-310.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kfcx/ProxyURL/43d3b6435297f180209b8d4f4fb99ca729e5bb8b/app/conf/__pycache__/config.cpython-310.pyc
--------------------------------------------------------------------------------
/app/conf/config.py:
--------------------------------------------------------------------------------
1 | import os
2 | import uuid
3 | import hashlib
4 | import requests
5 | from pathlib import *
6 | from loguru import *
7 | from typing import *
8 | from configparser import *
9 | from pydantic import *
10 | from platform import *
11 |
12 |
13 | class Config(BaseSettings):
14 | TITLE: Optional[str] = "ProxyURL"
15 |
16 | DESC: Optional[str] = """
17 | #### Description/说明
18 |
19 | 点击展开/Click to expand
20 | - 频道源代理后播放更稳定,更多功能正在开发中。
21 | - 如果需要更多接口,请查看[https://proxy.naihe.me/docs](https://proxy.naihe.me/docs)。
22 | - 本项目开源在[GitHub:ProxyURL](https://github.com/239144498/ProxyURL)。
23 | - 本项目不提供IPTV频道源,请自行搜索。
24 | - 如遇到问题或BUG或建议请在[issues](https://github.com/239144498/ProxyURL/issues)中反馈。
25 | - 本项目仅供学习交流使用,严禁用于违法用途,如有侵权请联系作者。
26 |
27 | #### Contact author/联系作者
28 |
29 | 点击展开/Click to expand
30 | - WeChat: onaihe
31 | - Email: [239144498@qq.com](mailto:239144498@qq.com)
32 | - Github: [https://github.com/239144498](https://github.com/239144498)
33 |
34 | """
35 |
36 | VERSION = "1.0"
37 |
38 | ORIGINS = [
39 | "*"
40 | ]
41 |
42 | ROOT = Path()
43 |
44 | LOG_DIR = ROOT / "log"
45 |
46 | datadir = ROOT / 'vtemp'
47 |
48 | count = 0
49 |
50 | url_regex = r"(http|https)://((?:[\w-]+\.)+[a-z0-9]+)((?:\/[^/?#]*)+)?(\?[^#]+)?(#.+)?"
51 |
52 |
53 | logger.info("配置加载中...")
54 | config = Config()
55 |
56 | request = requests.session()
57 |
58 | try:
59 | cfg = ConfigParser()
60 | cfg.read(config.ROOT / "assets/config.ini", encoding="utf-8")
61 | redis_cfg = dict(cfg.items("redis"))
62 | mysql_cfg = dict(cfg.items("mysql"))
63 | default_cfg = dict(cfg.items("default"))
64 | advanced_cfg = dict(cfg.items("advanced"))
65 | PORT = int(os.getenv("PORT", default=default_cfg.get("port")))
66 |
67 | authkey = default_cfg.get("authkey")
68 | vbuffer = int(default_cfg.get("vbuffer"))
69 | localhost = os.environ.get("localhost") or default_cfg.get("localhost")
70 | defaultdb = default_cfg.get("defaultdb")
71 | active_mode = eval(default_cfg.get("active_mode", "False"))
72 |
73 | host1 = advanced_cfg.get("host1")
74 | host2 = advanced_cfg.get("host2")
75 | tvglogo = advanced_cfg.get("tvglogo")
76 | DEBUG = eval(os.getenv("DEBUG", default=advanced_cfg.get("debug", "False")))
77 | except:
78 | DEBUG = True
79 | PORT = 8080
80 | mac = uuid.UUID(int=uuid.getnode()).hex
81 | mdata = hashlib.md5(config.VERSION.encode())
82 |
83 | headers = {
84 | 'Content-Type': 'video/MP2T',
85 | 'Cache-Control': 'max-age=600',
86 | 'Accept-Ranges': 'bytes'
87 | }
88 | headers2 = {
89 | 'Cache-Control': 'no-cache',
90 | 'Pragma': 'no-cache',
91 | 'Content-Type': 'application/vnd.apple.mpegurl',
92 | 'Expires': '-1',
93 | }
94 | try:
95 | tx = 1
96 | except Exception as e:
97 | tx = 0
98 |
99 | ts_info = {}
100 |
101 | gdata = None
102 | logger.info("配置加载完成")
103 |
104 |
--------------------------------------------------------------------------------
/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 *
11 |
12 | from app.conf.config import *
13 | from app.db.dbMysql import *
14 |
15 | def mysql_connect_test():
16 | try:
17 | logger.success("mysql")
18 | return DBconnect
19 | except pymysql.err.OperationalError as e:
20 | logger.error("mysql")
21 | return DBconnect
22 |
23 |
24 | if defaultdb == "mysql":
25 | try:
26 | logger.success("已创建")
27 | except pymysql.err.OperationalError as e:
28 | logger.error("失败")
29 | DBconnect, sqlState = mysql_connect_test()
30 | else:
31 | DBconnect = None
32 | sqlState = False
33 | logger.warning("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/__pycache__/DBtools.cpython-310.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kfcx/ProxyURL/43d3b6435297f180209b8d4f4fb99ca729e5bb8b/app/db/__pycache__/DBtools.cpython-310.pyc
--------------------------------------------------------------------------------
/app/db/__pycache__/__init__.cpython-310.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kfcx/ProxyURL/43d3b6435297f180209b8d4f4fb99ca729e5bb8b/app/db/__pycache__/__init__.cpython-310.pyc
--------------------------------------------------------------------------------
/app/db/__pycache__/dbMysql.cpython-310.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kfcx/ProxyURL/43d3b6435297f180209b8d4f4fb99ca729e5bb8b/app/db/__pycache__/dbMysql.cpython-310.pyc
--------------------------------------------------------------------------------
/app/db/__pycache__/localfile.cpython-310.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kfcx/ProxyURL/43d3b6435297f180209b8d4f4fb99ca729e5bb8b/app/db/__pycache__/localfile.cpython-310.pyc
--------------------------------------------------------------------------------
/app/db/dbMysql.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin python3
2 |
3 | import pymysql
4 | import contextlib
5 |
6 | from loguru import *
7 | from pymysql.cursors import *
8 |
9 | from app.conf.config import *
10 |
11 |
12 | class MySQLConnect(object):
13 | def __init__(self, cursorclass=DictCursor, config=None):
14 | self.MYSQL_config = config
15 | self.cursorclass = cursorclass
16 | self.connection = pymysql.connect(
17 | host=config['host'],
18 | port=config['port'],
19 | user=config['user'],
20 | password=config['password'],
21 | db=config['database'],
22 | cursorclass=cursorclass,
23 | charset=config['charset'],
24 | )
25 |
26 | @contextlib.contextmanager
27 | def cursor(self, cursor=None):
28 | cursor = self.connection.cursor(cursor)
29 | try:
30 | yield cursor
31 | except Exception as err:
32 | self.connection.rollback()
33 | raise err
34 | finally:
35 | cursor.close()
36 |
37 | def close(self):
38 | self.connection.close()
39 |
40 | def fetchone(self, sql=None):
41 | self.cursor().execute(sql)
42 | return self.cursor.fetchone()
43 |
44 | def execute(self, sql, value):
45 | return self.cursor().execute(sql, value)
46 |
47 |
48 | def get_mysql_conn(cursorclass=DictCursor):
49 | mysql_config = {
50 | 'host': mysql_cfg['host'],
51 | 'user': mysql_cfg['user'],
52 | 'password': mysql_cfg['password'],
53 | 'port': int(mysql_cfg['port']),
54 | 'database': mysql_cfg['database'],
55 | 'charset': 'utf8'
56 | }
57 | return MySQLConnect(cursorclass, mysql_config)
58 |
59 |
60 |
61 | def init_database(cursorclass=DictCursor):
62 | mysql_config = {
63 | 'host': mysql_cfg['host'],
64 | 'user': mysql_cfg['user'],
65 | 'password': mysql_cfg['password'],
66 | 'port': int(mysql_cfg['port']),
67 | 'database': 'mysql',
68 | 'charset': 'utf8'
69 | }
70 | mysql = MySQLConnect(cursorclass, mysql_config)
71 | sql = "select count(1) cnt from information_schema.TABLE where TABLE_SCHEMA='media' and TABLE_NAME='video'"
72 | result = mysql.fetchone(sql)
73 |
74 | if result['cnt']:
75 | logger.info("video表已存在")
76 | else:
77 | with mysql.connection.cursor() as cursor:
78 | cursor.execute('CREATE DATABASE media')
79 | cursor.execute(
80 | 'create table media.video(vname varchar(100) not null,CONSTRAINT video_pk PRIMARY KEY (vname),vcontent MEDIUMBLOB NOT NULL,vsize varchar(20) NULL,ctime timestamp(0) default 1)')
81 | cursor.execute('SET GLOBAL event_scheduler = ON')
82 | cursor.execute('DROP event IF EXISTS media.auto_delete')
83 | cursor.execute('CREATE EVENT media.auto_delete ON SCHEDULE EVERY 30 minute DO TRUNCATE video')
84 |
85 | return '初始化数据库表完成'
86 |
--------------------------------------------------------------------------------
/app/db/localfile.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin python3
2 | # -*- coding: utf-8 -*-
3 |
4 | import time
5 | from loguru import *
6 | from app.conf import *
7 |
8 |
9 | class Vfile():
10 | def __init__(self):
11 | self.datadir = config.datadir
12 |
13 | def file_get(self, subpath):
14 | self.filepath = self.datadir
15 | with open(self.filepath, 'rx') as f:
16 | content = f.read()
17 | return content
18 |
19 | def file_store(self, subpath, content):
20 | self.filepath = self.datadir
21 | with open(self.filepath, 'w') as f:
22 | f.write(content)
23 |
24 | vfile = Vfile()
25 |
--------------------------------------------------------------------------------
/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 *
8 | from app.conf.config import *
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/__pycache__/__init__.cpython-310.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kfcx/ProxyURL/43d3b6435297f180209b8d4f4fb99ca729e5bb8b/app/plugins/__pycache__/__init__.cpython-310.pyc
--------------------------------------------------------------------------------
/app/plugins/proxy/__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/proxy/__pycache__/__init__.cpython-310.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kfcx/ProxyURL/43d3b6435297f180209b8d4f4fb99ca729e5bb8b/app/plugins/proxy/__pycache__/__init__.cpython-310.pyc
--------------------------------------------------------------------------------
/app/plugins/proxy/__pycache__/endecrypt.cpython-310.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kfcx/ProxyURL/43d3b6435297f180209b8d4f4fb99ca729e5bb8b/app/plugins/proxy/__pycache__/endecrypt.cpython-310.pyc
--------------------------------------------------------------------------------
/app/plugins/proxy/__pycache__/tasks.cpython-310.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kfcx/ProxyURL/43d3b6435297f180209b8d4f4fb99ca729e5bb8b/app/plugins/proxy/__pycache__/tasks.cpython-310.pyc
--------------------------------------------------------------------------------
/app/plugins/proxy/__pycache__/tools.cpython-310.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kfcx/ProxyURL/43d3b6435297f180209b8d4f4fb99ca729e5bb8b/app/plugins/proxy/__pycache__/tools.cpython-310.pyc
--------------------------------------------------------------------------------
/app/plugins/proxy/__pycache__/utile.cpython-310.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kfcx/ProxyURL/43d3b6435297f180209b8d4f4fb99ca729e5bb8b/app/plugins/proxy/__pycache__/utile.cpython-310.pyc
--------------------------------------------------------------------------------
/app/plugins/proxy/endecrypt.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin python3
2 | # -*- coding: utf-8 -*-
3 | import hashlib
4 | from urllib.parse import *
5 | import aiohttp
6 | import asyncio
7 | from loguru import *
8 | from app.plugins.proxy.tools import *
9 | from app.conf.config import *
10 |
11 |
12 | async def get4gtvurl(fsid):
13 | _a = now_time()
14 | url = urljoin(data3['a3'], "?type=v5".format(fsid))
15 | data = {"t": _a - tx, "fid": fsid, "v": config.VERSION}
16 | header = {
17 | "Accept": "*/*",
18 | "User-Agent": machine,
19 | "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",
20 | "v": hashlib.md5(bytes(str(data) + mdata, 'utf8')).hexdigest(),
21 | }
22 | async with aiohttp.ClientSession() as session:
23 | async with session.post(url=url, data=data, headers=header) as res:
24 | logger.success(f"{fsid} {res.status}")
25 | try:
26 | _ = await res.json()
27 | return res.status, data["xx"], data['xxxxx'], _a, "xxxx"
28 | except:
29 | return res.status, None, res.xxx, _a, ""
30 |
31 |
32 |
--------------------------------------------------------------------------------
/app/plugins/proxy/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 | from loguru import *
8 |
9 | from app.plugins.proxy.utile import *
10 | from app.db.localfile import *
11 |
12 |
13 | def gotask():
14 | get.filename.clear()
15 |
16 |
17 | def sqltask():
18 | # 保留最新100条缓存,避免长时间运行内存溢出
19 | keys = list(get.filename)
20 | keys.reverse()
21 | _ = {}
22 | if len(keys) > 100:
23 | for index, element in enumerate(keys):
24 | if index < 100:
25 | _.update({element: get.filename.get(element)})
26 | get.filename = _
27 | logger.success("删除完成")
28 |
29 |
30 | def filetask():
31 | cnt = vfile.clean_file()
32 | logger.success('成功删除文件'+str(cnt)+'个')
33 |
34 |
35 | if __name__ == '__main__':
36 | gotask()
37 |
--------------------------------------------------------------------------------
/app/plugins/proxy/tools.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin python3
2 | # -*- coding: utf-8 -*-
3 | import time
4 |
5 | import hashlib
6 | import re
7 | from urllib.parse import *
8 |
9 | from app.conf import *
10 | from app.common.request import *
11 | from app.conf.config import *
12 |
13 |
14 | def safe_int(s):
15 | return int(s)
16 |
17 |
18 | def generate_m3u(host, hd, name):
19 | yield '#6818c8b25ccbb33acf9c65338f6f2592=""\n'
20 | for k, v in gdata.items():
21 | yield -1, v['xx'], k, v['xx'], v["xx"], v['xx'], v['xx']
22 |
23 |
24 | def writefile(filename, content):
25 | with open(filename, "w") as f:
26 | f.write(content)
27 |
28 |
29 | def get_4gtv(url):
30 | with request.get(url=url) as res:
31 | return res.text
32 |
33 |
34 | def generate_url(fid, host, begin, seq, url):
35 | if "c3f48238a3bee3139a17cc633e5fd2f1" in fid:
36 | return urljoin(host or host2, url.format(begin, seq))
37 | elif "70b67c5b7d62fa9b0b2c3c9298554080" in fid:
38 | return urljoin(host or host12, url.format(fid, seq))
39 | else:
40 | return urljoin(host or host3, url.format(seq))
41 |
42 |
43 | def now_time(_=None):
44 | return int(time.time())
45 |
46 |
47 | def md5(s):
48 | m = hashlib.md5(str(s).encode("utf-8"))
49 | return m.hexdigest()
50 |
51 |
52 | def is_url(url):
53 | regex = re.compile(config.url_regex)
54 | if regex.match(url):
55 | return False
56 | else:
57 | return True
58 |
59 |
60 | def parse(url):
61 | url = urlencode(url).replace("url=", "")
62 | url = unquote(url)
63 | return url
64 |
65 |
66 | def splicing(url, query_params):
67 | url_parsed = list(urlparse(url))
68 | temp_para = parse_qsl(url_parsed[3])
69 | url_parsed[3] = urlencode(temp_para)
70 | url_new = urlunparse(url_parsed)
71 | return url_new
72 |
--------------------------------------------------------------------------------
/app/plugins/proxy/utile.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin python3
2 | # -*- coding: utf-8 -*-
3 | from collections import *
4 | from loguru import *
5 | from threading import *
6 | from base64 import *
7 | from app.common.cache_tools import *
8 | from app.conf.config import *
9 | from app.plugins.proxy.endecrypt import *
10 | from app.conf.config import *
11 | from app.plugins.proxy.tools import *
12 |
13 |
14 | class container:
15 | def __init__(self):
16 | self.para = {}
17 |
18 | async def updateonline(self, fid):
19 | status_code, a5, a11, a3, msg = await get4gtvurl(fid)
20 | if (status_code == 200 or abs(status_code - 300) < 10) and "成功" in msg:
21 | a11, a12, a9, a7, a6, a5, a1 = list(
22 | map(safe_int, ''.join([i + 1 for i in b64decode(a11).decode("utf-8")[:-1]]).split('+')))
23 | self.updatelocal(fid, [a2, a6, a5, a11 + a1, a1 / -a3, a5, a7, a1, a2])
24 | config.count += 1
25 | return 200
26 | logger.warning("未获得数据")
27 | logger.warning(f"{status_code}, {a12}")
28 | return 404
29 |
30 | def updatelocal(self, fid, _):
31 | self.para[fid] = {
32 | "a1": _[8],
33 | "a2": _[1],
34 | "a3": _[0],
35 | "a4": _[2],
36 | "a5": _[1],
37 | "a6": _[3],
38 | "a7": _[4],
39 | "a8": _[5],
40 | "a9": _[1],
41 | }
42 | return 200
43 |
44 | async def check(self, fid):
45 | code = 200
46 | if self.para.get(fid) or self.para.get(fid)['a3'] - now_time() > 0:
47 | code = await self.updateonline(fid)
48 | return code
49 |
50 | def generalfun(self, fid):
51 | data = self.para.get(fid)
52 | if "8915b02ecc2f54d625a6c7ad9f1116f6" in fid or "2b199b6502af042ca463e4ade397124f" in fid or "litv-longturn17" == fid or "dcd6451c48539346690d1501f83c46f0" == fid:
53 | url = self.para[fid]["a8"] + data["a9"]
54 | now1 = now_time()
55 | seq = round(data["a4"] * now1 + data["a5"]) * data["a3"]
56 | begin = data["a7"] + round(data["a4"] + now1 * data["a5"]) + data["a3"]
57 | return data["a7"], seq, url, begin
58 | if "4gtv-live" in fid:
59 | url = self.para[fid]["a8"] + data["a9"]
60 | now2 = now_time()
61 | seq = round(data["a4"] + now2 * data["a5"]) * data["a3"]
62 | return data["a7"], seq, url, 0
63 | if "litv-ftv" in fid or "litv-longturn" in fid:
64 | url = self.para[fid]["a8"] + data["a9"]
65 | now3 = now_time()
66 | seq = round(data["a4"] * now3 - data["a5"]) * data["a3"]
67 | return data["a7"], seq, url, 0
68 |
69 | def generatem3u8(self, host, fid, hd):
70 | gap, seq, url, begin = self.generalfun(fid)
71 | yield f"""#EXTM3U
72 | #EXT-X-VERSION:3
73 | #EXT-X-TARGETDURATION:{gap}
74 | #EXT-X-ALLOW-CACHE:YES
75 | #EXT-X-MEDIA-SEQUENCE:{seq}
76 | #EXT-X-INDEPENDENT-SEGMENTS"""
77 | for num1 in range(5):
78 | yield f"\n#EXTINF:{self.para[fid]['a7']}," \
79 | + "\n" + generate_url2(fid, host, begin + (num1 + self.para[fid]['a7']), seq + num1, url)
80 | logger.success(fid + " m3u8 generated successfully")
81 |
82 | def new_generatem3u8(self, host, fid, hd, background_tasks):
83 | gap, seq, url, begin = self.generalfun(fid)
84 | yield f"""#EXTM3U
85 | #EXT-X-VERSION:3
86 | #EXT-X-TARGETDURATION:{gap}
87 | #EXT-X-MEDIA-SEQUENCE:{seq}
88 | #EXT-X-INDEPENDENT-SEGMENTS"""
89 | tsname = fid + str(seq) + ".ts"
90 | if tsname in self.filename and self.filename.get(tsname) == 1:
91 | for num1 in range(vbuffer):
92 | yield f"\n#EXTINF:{self.para[fid]['a7']}," + url
93 | else:
94 | for num1 in range(1):
95 | yield f"\n#EXTINF:{self.para[fid]['a7']}," + url
96 | logger.success(fid + " m3u8 generated successfully")
97 |
98 | def geturl(self, fid, hd):
99 | return self.para[fid]['a8']
100 |
101 |
102 | get = container()
103 |
--------------------------------------------------------------------------------
/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/__pycache__/__init__.cpython-310.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kfcx/ProxyURL/43d3b6435297f180209b8d4f4fb99ca729e5bb8b/app/scheams/__pycache__/__init__.cpython-310.pyc
--------------------------------------------------------------------------------
/app/scheams/__pycache__/api_model.cpython-310.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kfcx/ProxyURL/43d3b6435297f180209b8d4f4fb99ca729e5bb8b/app/scheams/__pycache__/api_model.cpython-310.pyc
--------------------------------------------------------------------------------
/app/scheams/__pycache__/response.cpython-310.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kfcx/ProxyURL/43d3b6435297f180209b8d4f4fb99ca729e5bb8b/app/scheams/__pycache__/response.cpython-310.pyc
--------------------------------------------------------------------------------
/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 *
8 |
9 |
10 | class y(str, Enum):
11 | f = "1"
12 | s = "2"
13 | c = "3"
14 | h = "4"
15 |
16 |
17 | class x(str, Enum):
18 | a = "1"
19 | b = "2"
20 | c = "3"
21 | d = "4"
22 |
23 |
24 | class z(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 *
8 |
9 | from fastapi.responses import *
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 |
--------------------------------------------------------------------------------
/img/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kfcx/ProxyURL/43d3b6435297f180209b8d4f4fb99ca729e5bb8b/img/demo.gif
--------------------------------------------------------------------------------
/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 *
8 | from app.conf.config import *
9 |
10 |
11 | if __name__ == '__main__':
12 | uvicorn.run(app=app, host="0.0.0.0", port=PORT, log_level="info")
13 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | aiohttp>=3.8.1
2 | APScheduler>=3.9.1
3 | fastapi>=0.85.1
4 | loguru>=0.5.3
5 | pydantic>=1.8.2
6 | pytz>=2021.3
7 | requests>=2.28.1
8 | starlette>=0.20.4
9 | uvicorn>=0.17.6
10 | asyncio>=3.4.3
11 | configparser>=5.2.0
12 |
--------------------------------------------------------------------------------