Pyckplayer 1.0.4__tar.gz → 1.0.7__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pyckplayer-1.0.4/src/Pyckplayer.egg-info → pyckplayer-1.0.7}/PKG-INFO +1 -1
- {pyckplayer-1.0.4 → pyckplayer-1.0.7}/setup.py +1 -1
- {pyckplayer-1.0.4 → pyckplayer-1.0.7/src/Pyckplayer.egg-info}/PKG-INFO +1 -1
- {pyckplayer-1.0.4 → pyckplayer-1.0.7}/src/pyckplayer/core.py +128 -5
- {pyckplayer-1.0.4 → pyckplayer-1.0.7}/LICENSE +0 -0
- {pyckplayer-1.0.4 → pyckplayer-1.0.7}/MANIFEST.in +0 -0
- {pyckplayer-1.0.4 → pyckplayer-1.0.7}/README.md +0 -0
- {pyckplayer-1.0.4 → pyckplayer-1.0.7}/pyproject.toml +0 -0
- {pyckplayer-1.0.4 → pyckplayer-1.0.7}/setup.cfg +0 -0
- {pyckplayer-1.0.4 → pyckplayer-1.0.7}/src/Pyckplayer.egg-info/SOURCES.txt +0 -0
- {pyckplayer-1.0.4 → pyckplayer-1.0.7}/src/Pyckplayer.egg-info/dependency_links.txt +0 -0
- {pyckplayer-1.0.4 → pyckplayer-1.0.7}/src/Pyckplayer.egg-info/entry_points.txt +0 -0
- {pyckplayer-1.0.4 → pyckplayer-1.0.7}/src/Pyckplayer.egg-info/requires.txt +0 -0
- {pyckplayer-1.0.4 → pyckplayer-1.0.7}/src/Pyckplayer.egg-info/top_level.txt +0 -0
- {pyckplayer-1.0.4 → pyckplayer-1.0.7}/src/pyckplayer/__init__.py +0 -0
- {pyckplayer-1.0.4 → pyckplayer-1.0.7}/src/pyckplayer/ckplayer/LICENSE +0 -0
- {pyckplayer-1.0.4 → pyckplayer-1.0.7}/src/pyckplayer/ckplayer/css/ckplayer.css +0 -0
- {pyckplayer-1.0.4 → pyckplayer-1.0.7}/src/pyckplayer/ckplayer/css/ckplayer.ixigua.css +0 -0
- {pyckplayer-1.0.4 → pyckplayer-1.0.7}/src/pyckplayer/ckplayer/css/ckplayer.red.css +0 -0
- {pyckplayer-1.0.4 → pyckplayer-1.0.7}/src/pyckplayer/ckplayer/css/images/adclose.png +0 -0
- {pyckplayer-1.0.4 → pyckplayer-1.0.7}/src/pyckplayer/ckplayer/css/images/buffer.png +0 -0
- {pyckplayer-1.0.4 → pyckplayer-1.0.7}/src/pyckplayer/ckplayer/css/images/ckplayer.48.png +0 -0
- {pyckplayer-1.0.4 → pyckplayer-1.0.7}/src/pyckplayer/ckplayer/css/images/ckplayer.png +0 -0
- {pyckplayer-1.0.4 → pyckplayer-1.0.7}/src/pyckplayer/ckplayer/css/images/ckplayer_ixigua.48.png +0 -0
- {pyckplayer-1.0.4 → pyckplayer-1.0.7}/src/pyckplayer/ckplayer/css/images/ckplayer_ixigua.png +0 -0
- {pyckplayer-1.0.4 → pyckplayer-1.0.7}/src/pyckplayer/ckplayer/css/images/ckplayer_red.48.png +0 -0
- {pyckplayer-1.0.4 → pyckplayer-1.0.7}/src/pyckplayer/ckplayer/css/images/ckplayer_red.png +0 -0
- {pyckplayer-1.0.4 → pyckplayer-1.0.7}/src/pyckplayer/ckplayer/css/images/favicon.ico +0 -0
- {pyckplayer-1.0.4 → pyckplayer-1.0.7}/src/pyckplayer/ckplayer/css/images/loading.png +0 -0
- {pyckplayer-1.0.4 → pyckplayer-1.0.7}/src/pyckplayer/ckplayer/css/images/logo.png +0 -0
- {pyckplayer-1.0.4 → pyckplayer-1.0.7}/src/pyckplayer/ckplayer/css/images/play.png +0 -0
- {pyckplayer-1.0.4 → pyckplayer-1.0.7}/src/pyckplayer/ckplayer/flv.js/LICENSE +0 -0
- {pyckplayer-1.0.4 → pyckplayer-1.0.7}/src/pyckplayer/ckplayer/flv.js/README.md +0 -0
- {pyckplayer-1.0.4 → pyckplayer-1.0.7}/src/pyckplayer/ckplayer/flv.js/flv.js +0 -0
- {pyckplayer-1.0.4 → pyckplayer-1.0.7}/src/pyckplayer/ckplayer/flv.js/flv.js.map +0 -0
- {pyckplayer-1.0.4 → pyckplayer-1.0.7}/src/pyckplayer/ckplayer/flv.js/flv.min.js +0 -0
- {pyckplayer-1.0.4 → pyckplayer-1.0.7}/src/pyckplayer/ckplayer/flv.js/flv.min.js.map +0 -0
- {pyckplayer-1.0.4 → pyckplayer-1.0.7}/src/pyckplayer/ckplayer/hls.js/LICENSE +0 -0
- {pyckplayer-1.0.4 → pyckplayer-1.0.7}/src/pyckplayer/ckplayer/hls.js/README.md +0 -0
- {pyckplayer-1.0.4 → pyckplayer-1.0.7}/src/pyckplayer/ckplayer/hls.js/hls-demo.js +0 -0
- {pyckplayer-1.0.4 → pyckplayer-1.0.7}/src/pyckplayer/ckplayer/hls.js/hls-demo.js.map +0 -0
- {pyckplayer-1.0.4 → pyckplayer-1.0.7}/src/pyckplayer/ckplayer/hls.js/hls.js +0 -0
- {pyckplayer-1.0.4 → pyckplayer-1.0.7}/src/pyckplayer/ckplayer/hls.js/hls.js.d.ts +0 -0
- {pyckplayer-1.0.4 → pyckplayer-1.0.7}/src/pyckplayer/ckplayer/hls.js/hls.js.map +0 -0
- {pyckplayer-1.0.4 → pyckplayer-1.0.7}/src/pyckplayer/ckplayer/hls.js/hls.light.js +0 -0
- {pyckplayer-1.0.4 → pyckplayer-1.0.7}/src/pyckplayer/ckplayer/hls.js/hls.light.js.map +0 -0
- {pyckplayer-1.0.4 → pyckplayer-1.0.7}/src/pyckplayer/ckplayer/hls.js/hls.light.min.js +0 -0
- {pyckplayer-1.0.4 → pyckplayer-1.0.7}/src/pyckplayer/ckplayer/hls.js/hls.light.min.js.map +0 -0
- {pyckplayer-1.0.4 → pyckplayer-1.0.7}/src/pyckplayer/ckplayer/hls.js/hls.min.js +0 -0
- {pyckplayer-1.0.4 → pyckplayer-1.0.7}/src/pyckplayer/ckplayer/hls.js/hls.min.js.map +0 -0
- {pyckplayer-1.0.4 → pyckplayer-1.0.7}/src/pyckplayer/ckplayer/js/ckplayer.js +0 -0
- {pyckplayer-1.0.4 → pyckplayer-1.0.7}/src/pyckplayer/ckplayer/js/ckplayer.min.js +0 -0
- {pyckplayer-1.0.4 → pyckplayer-1.0.7}/src/pyckplayer/ckplayer/language/en.js +0 -0
- {pyckplayer-1.0.4 → pyckplayer-1.0.7}/src/pyckplayer/ckplayer/language/zh.cn.js +0 -0
- {pyckplayer-1.0.4 → pyckplayer-1.0.7}/src/pyckplayer/ckplayer/language/zh.hk.js +0 -0
- {pyckplayer-1.0.4 → pyckplayer-1.0.7}/src/pyckplayer/ckplayer/mpegts.js/LICENSE +0 -0
- {pyckplayer-1.0.4 → pyckplayer-1.0.7}/src/pyckplayer/ckplayer/mpegts.js/mpegts.js +0 -0
- {pyckplayer-1.0.4 → pyckplayer-1.0.7}/src/pyckplayer/ckplayer/mpegts.js/mpegts.js.map +0 -0
- {pyckplayer-1.0.4 → pyckplayer-1.0.7}/src/pyckplayer/ckplayer/mpegts.js/mpegts.min.js +0 -0
- {pyckplayer-1.0.4 → pyckplayer-1.0.7}/src/pyckplayer/cli.py +0 -0
|
@@ -6,7 +6,12 @@ import json
|
|
|
6
6
|
import atexit
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
from urllib.parse import quote
|
|
9
|
-
from typing import Optional, Tuple, Union
|
|
9
|
+
from typing import Optional, Tuple, Union, Dict, Any
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
import cv2
|
|
13
|
+
except ImportError:
|
|
14
|
+
cv2 = None
|
|
10
15
|
|
|
11
16
|
|
|
12
17
|
DEFAULT_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36"
|
|
@@ -91,8 +96,106 @@ def download_cover(poster_url: Optional[str], silent: bool = True) -> Optional[s
|
|
|
91
96
|
return None
|
|
92
97
|
|
|
93
98
|
|
|
99
|
+
def is_streaming_protocol(url: str) -> bool:
|
|
100
|
+
streaming_protocols = [
|
|
101
|
+
'rtsp://', 'rtmp://', 'rtmps://', 'rtmpt://', 'rtmpte://',
|
|
102
|
+
'http://', 'https://'
|
|
103
|
+
]
|
|
104
|
+
url_lower = url.lower()
|
|
105
|
+
return any(url_lower.startswith(protocol) for protocol in streaming_protocols)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def get_content_type(url: str) -> Optional[str]:
|
|
109
|
+
try:
|
|
110
|
+
response = requests.head(url, timeout=5, allow_redirects=True)
|
|
111
|
+
if 'Content-Type' in response.headers:
|
|
112
|
+
return response.headers['Content-Type']
|
|
113
|
+
except Exception:
|
|
114
|
+
pass
|
|
115
|
+
return None
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def is_streaming_content(content_type: str) -> bool:
|
|
119
|
+
streaming_content_types = [
|
|
120
|
+
'application/vnd.apple.mpegurl',
|
|
121
|
+
'application/dash+xml',
|
|
122
|
+
'audio/mpegurl',
|
|
123
|
+
'video/mp4',
|
|
124
|
+
'video/flv',
|
|
125
|
+
'video/quicktime',
|
|
126
|
+
'application/x-mpegURL',
|
|
127
|
+
'application/octet-stream'
|
|
128
|
+
]
|
|
129
|
+
if content_type:
|
|
130
|
+
content_type_lower = content_type.lower()
|
|
131
|
+
return any(ct in content_type_lower for ct in streaming_content_types)
|
|
132
|
+
return False
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def get_video_info(video_url: str) -> Optional[Dict[str, Any]]:
|
|
136
|
+
if cv2 is None:
|
|
137
|
+
return None
|
|
138
|
+
|
|
139
|
+
cap = cv2.VideoCapture(video_url)
|
|
140
|
+
if not cap.isOpened():
|
|
141
|
+
return None
|
|
142
|
+
|
|
143
|
+
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
|
|
144
|
+
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
|
|
145
|
+
fps = cap.get(cv2.CAP_PROP_FPS)
|
|
146
|
+
frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
|
|
147
|
+
duration = frame_count / fps if fps > 0 else 0
|
|
148
|
+
fourcc_code = int(cap.get(cv2.CAP_PROP_FOURCC))
|
|
149
|
+
fourcc = (
|
|
150
|
+
chr((fourcc_code >> 0) & 0xFF)
|
|
151
|
+
+ chr((fourcc_code >> 8) & 0xFF)
|
|
152
|
+
+ chr((fourcc_code >> 16) & 0xFF)
|
|
153
|
+
+ chr((fourcc_code >> 24) & 0xFF)
|
|
154
|
+
)
|
|
155
|
+
bitrate = int(cap.get(cv2.CAP_PROP_BITRATE))
|
|
156
|
+
|
|
157
|
+
cap.release()
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
"width": width,
|
|
161
|
+
"height": height,
|
|
162
|
+
"resolution": f"{width}x{height}",
|
|
163
|
+
"fps": round(fps, 2),
|
|
164
|
+
"frame_count": frame_count,
|
|
165
|
+
"duration": round(duration, 2),
|
|
166
|
+
"fourcc": fourcc,
|
|
167
|
+
"bitrate": bitrate,
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def is_live_url(url: str) -> bool:
|
|
172
|
+
url_lower = url.lower()
|
|
173
|
+
live_keywords = [
|
|
174
|
+
'/live/', '/stream/', '/broadcast/', '/live-stream/',
|
|
175
|
+
'live.', 'stream.', 'live_', 'stream_',
|
|
176
|
+
'?live', '&live', '=live',
|
|
177
|
+
'douyincdn.com', 'live.kuaishou.com', 'live.bilibili.com',
|
|
178
|
+
'live.douyin.com', 'live.qq.com', 'live.weibo.com'
|
|
179
|
+
]
|
|
180
|
+
if any(keyword in url_lower for keyword in live_keywords):
|
|
181
|
+
return True
|
|
182
|
+
|
|
183
|
+
video_info = get_video_info(url)
|
|
184
|
+
if video_info is not None:
|
|
185
|
+
duration = video_info.get("duration", 0)
|
|
186
|
+
if duration < 0:
|
|
187
|
+
return True
|
|
188
|
+
|
|
189
|
+
content_type = get_content_type(url)
|
|
190
|
+
if content_type:
|
|
191
|
+
return is_streaming_content(content_type)
|
|
192
|
+
|
|
193
|
+
return False
|
|
194
|
+
|
|
195
|
+
|
|
94
196
|
def detect_stream_type(url: str) -> str:
|
|
95
197
|
url_lower = url.lower()
|
|
198
|
+
|
|
96
199
|
if '.m3u8' in url_lower or '/hls/' in url_lower:
|
|
97
200
|
return 'hls'
|
|
98
201
|
elif '.flv' in url_lower:
|
|
@@ -101,8 +204,22 @@ def detect_stream_type(url: str) -> str:
|
|
|
101
204
|
return 'ts'
|
|
102
205
|
elif '.mp4' in url_lower:
|
|
103
206
|
return 'mp4'
|
|
104
|
-
|
|
105
|
-
return '
|
|
207
|
+
elif url_lower.startswith('rtsp://'):
|
|
208
|
+
return 'rtsp'
|
|
209
|
+
elif url_lower.startswith('rtmp://') or url_lower.startswith('rtmps://'):
|
|
210
|
+
return 'rtmp'
|
|
211
|
+
|
|
212
|
+
content_type = get_content_type(url)
|
|
213
|
+
if content_type:
|
|
214
|
+
content_type_lower = content_type.lower()
|
|
215
|
+
if 'mpegurl' in content_type_lower or 'hls' in content_type_lower:
|
|
216
|
+
return 'hls'
|
|
217
|
+
elif 'flv' in content_type_lower:
|
|
218
|
+
return 'flv'
|
|
219
|
+
elif 'dash' in content_type_lower:
|
|
220
|
+
return 'dash'
|
|
221
|
+
|
|
222
|
+
return 'auto'
|
|
106
223
|
|
|
107
224
|
|
|
108
225
|
def generate_html(video_url: str,
|
|
@@ -114,6 +231,7 @@ def generate_html(video_url: str,
|
|
|
114
231
|
favicon_path = quote((script_dir / "ckplayer" / "css" / "images" / "logo.png").as_posix())
|
|
115
232
|
|
|
116
233
|
stream_type = detect_stream_type(video_url)
|
|
234
|
+
is_live = is_live_url(video_url)
|
|
117
235
|
|
|
118
236
|
video_object = {
|
|
119
237
|
"container": "#player",
|
|
@@ -126,12 +244,17 @@ def generate_html(video_url: str,
|
|
|
126
244
|
|
|
127
245
|
if stream_type == 'hls':
|
|
128
246
|
video_object["plug"] = "hls.js"
|
|
129
|
-
|
|
247
|
+
if is_live:
|
|
248
|
+
video_object["live"] = True
|
|
130
249
|
elif stream_type == 'flv':
|
|
131
250
|
video_object["plug"] = "flv.js"
|
|
132
|
-
|
|
251
|
+
if is_live:
|
|
252
|
+
video_object["live"] = True
|
|
133
253
|
elif stream_type == 'ts':
|
|
134
254
|
video_object["plug"] = "mpegts.js"
|
|
255
|
+
if is_live:
|
|
256
|
+
video_object["live"] = True
|
|
257
|
+
elif stream_type == 'mp4' and is_live:
|
|
135
258
|
video_object["live"] = True
|
|
136
259
|
|
|
137
260
|
if poster_url:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyckplayer-1.0.4 → pyckplayer-1.0.7}/src/pyckplayer/ckplayer/css/images/ckplayer_ixigua.48.png
RENAMED
|
File without changes
|
{pyckplayer-1.0.4 → pyckplayer-1.0.7}/src/pyckplayer/ckplayer/css/images/ckplayer_ixigua.png
RENAMED
|
File without changes
|
{pyckplayer-1.0.4 → pyckplayer-1.0.7}/src/pyckplayer/ckplayer/css/images/ckplayer_red.48.png
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|