Skip to content

Commit

Permalink
重定向下一集
Browse files Browse the repository at this point in the history
  • Loading branch information
kjtsune committed Mar 27, 2024
1 parent e2819d8 commit aee651f
Show file tree
Hide file tree
Showing 6 changed files with 127 additions and 39 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@ https://github.com/kjtsune/embyToLocalPlayer#faq
> 模拟 302 重定向视频流
* 若使用预读取下一集,nginx 可以只反代视频流。浏览器访问源站,重定向视频流交给本机。降低 nginx 配置难度。减少 bug。
* 亦可用于采用播放分离的公益服,视频流线路本地重定向。加速访问。
* 亦可用于其他重定向视频流服务器。采用本地重定向。加速访问。
* 填写位置:`.ini` > dev
```
# 网址之间逗号隔开,成对填写。源站, 反代站。
Expand Down Expand Up @@ -508,6 +508,7 @@ https://github.com/kjtsune/embyToLocalPlayer#faq
<details>
<summary>Pot 漏播第零季选集</summary>
* 若配置好但不生效,属于正常,不用反馈。
* 修复情景:Pot 读盘模式播放动漫第一季,会漏播 Emby 穿插的 S0 集数。
* 前提条件:Pot 选项 > 基本 > 相似文件打开策略 > 仅打开选定的文件。(由脚本添加播放列表)
* 填写位置:`.ini` > playlist
Expand Down
3 changes: 2 additions & 1 deletion embyToLocalPlayer_config.ini
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ subtitle_priority = Chinese Simplified, Chinese, 中文, ASS, SRT, Und

# 需要播放前检查并缓存,视频流重定向链接的,服务器域名的关键词,逗号隔开。
# 适用于前后端分离等,视频流会重定向的服务器。若随意启用,会多一次请求来检查,反而慢。
# 对非硬字幕的网络视频流,会节省两次或以上的网络请求,提高起播速度。(限当前视频)
# 对非硬字幕的网络视频流,会节省两次或以上的重定向请求,提高起播速度。(播放进度超过一半时获取下一集重定向,限 mpv)
# 若重定向只替换了域名,建议使用本地重定向功能,详见 FAQ > 隐藏功能 > 模拟 302 重定向。
redirect_check_host =

# 视频有多个版本时,尝试根据文件名关键词选择版本。仅在网页未选中版本时生效,例如:首页点击播放。
Expand Down
24 changes: 10 additions & 14 deletions user_script/embyToLocalPlayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// @name:zh-CN embyToLocalPlayer
// @name:en embyToLocalPlayer
// @namespace https://github.com/kjtsune/embyToLocalPlayer
// @version 1.1.13.3
// @version 2024.03.27
// @description 需要 Python。Emby/Jellyfin 调用外部本地播放器,并回传播放记录。适配 Plex。
// @description:zh-CN 需要 Python。Emby/Jellyfin 调用外部本地播放器,并回传播放记录。适配 Plex。
// @description:en Require Python. Play in an external player. Update watch history to Emby/Jellyfin server. Support Plex.
Expand All @@ -22,6 +22,13 @@
// ==/UserScript==
'use strict';
/*
2024.03.27:
1. 预重定向下一集视频流 url (配置文件有新增条目 [dev])
* 版本间累积更新:
* 可播放前检查视频流重定向。
* 新版 mpv 播放列表报错。@verygoodlee
* 修了些回传失败的情况。
* 适配 Emby 全部播放/随机播放/播放列表 (油猴也需要更新,限电影和音乐视频类型)
2024-1-2:
1. 适配 Emby 跳过简介/片头。(限 mpv,且视频本身无章节,通过添加章节实现。)
* 版本间累积更新:
Expand All @@ -30,19 +37,6 @@
2023-12-11:
1. 美化 mpv pot 标题。
2. 改善版本筛选逻辑。
2023-12-07:
1. mpv 播放列表避免与官方 autoload.lua 冲突。
2. pot 修复读盘模式播放列表漏播第零季选集。(详见 FAQ 隐藏功能)
* 版本间累积更新:
* 追更 TG 通知。(详见 FAQ 隐藏功能)
* 适配 Emby beta 版本 api 变动。
* bgm.tv: 适配上游搜索结果变动。
* bgm.tv: 增加旧版搜索 api 备选。
* trakt: 适配上游新剧缺失单集 imdb/tvdb。
* .bat 强制使用 utf-8 编码。
* 默认启用日志文件。
* pot 播放列表 未加载完成时可退出。
* 网络流:外挂 sup 支持(限 Emby v4.8.0.55 | mpv)。升级 Emby 记得备份,无法回退。
*/
(function () {
'use strict';
Expand Down Expand Up @@ -276,6 +270,8 @@
mainEpInfo: mainEpInfo,
episodesInfo: episodesInfoData,
playlistInfo: playlistData,
gmInfo: GM_info,
userAgent: navigator.userAgent,
}
playlistInfoCache = null;
logger.info(extraData);
Expand Down
14 changes: 9 additions & 5 deletions utils/net_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,28 +121,32 @@ def http_error_301(self, req, fp, code, msg, hdrs):
return


def get_redirect_url(url, key_trim='PlaySessionId'):
def get_redirect_url(url, key_trim='PlaySessionId', follow_redirect=False):
jump_url = url
key = url.split(key_trim)[0] if key_trim else url
if cache := redirect_url_cache.get(key):
return cache
start = time.time()
try:
redirect_handler = FollowHTTPRedirectHandler if follow_redirect else SkipHTTPRedirectHandler
# FollowHTTPRedirectHandler, # 系统代理有可能很慢,默认不启用
timeout = 15 if follow_redirect else 3
handlers = [
urllib.request.HTTPSHandler(context=ssl_context),
SkipHTTPRedirectHandler,
# FollowHTTPRedirectHandler, # 可能和系统代理冲突,不启用
redirect_handler,
]
opener = urllib.request.build_opener(*handlers)
jump_url = opener.open(requests_urllib(url, req_only=True), timeout=3).url
jump_url = opener.open(requests_urllib(url, req_only=True), timeout=timeout).url
except urllib.error.HTTPError as e:
if e.code == 302:
jump_url = e.headers['Location']
elif e.code == 301:
jump_url = e.url
else:
logger.error(f'{e.code=} get_redirect_url: {str(e)[:100]}')
jump_url = e.url
except Exception as e:
logger.error(f'code={getattr(e, "code")} get_redirect_url: {str(e)[:100]}')
logger.error(f'disable redirect: code={getattr(e, "code", None)} get_redirect_url: {str(e)[:100]}')
logger.info(f'get_redirect_url: used time={str(time.time() - start)[:4]}')
redirect_url_cache[key] = jump_url
return jump_url
Expand Down
109 changes: 91 additions & 18 deletions utils/players.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@
from utils.configs import configs, MyLogger
from utils.downloader import Downloader
from utils.net_tools import (requests_urllib, update_server_playback_progress, sync_third_party_for_eps, list_episodes,
save_sub_file)
save_sub_file, get_redirect_url)
from utils.python_mpv_jsonipc import MPV
from utils.tools import activate_window_by_pid

logger = MyLogger()
prefetch_data = dict(on=True, running=False, stop_sec_dict={}, done_list=[], playlist_data={})
prefetch_data = dict(on=True, running=False, stop_sec_dict={}, done_list=[])
pipe_port_stack = list(reversed(range(25)))
mpv_play_speed = {'media_title': 'speed'}

Expand Down Expand Up @@ -59,17 +59,84 @@ def playlist_add(self, eps_data=None):
self.player_kwargs['limit'] = limit
self.playlist_data = playlist_fun[self.player_name](data=self.data, eps_data=eps_data, **self.player_kwargs)

prefetch_data['playlist_data'] = self.playlist_data
threading.Thread(target=self.prefetch_next_ep_loop, daemon=True).start()
threading.Thread(target=self.redirect_next_ep_loop, daemon=True).start()

@staticmethod
def prefetch_next_ep_loop():
def redirect_next_ep_loop(self):
# 未兼容播放器多开,暂不处理
mpv = self.player_kwargs.get('mpv')
if not mpv or not configs.check_str_match(self.data['netloc'], 'dev', 'redirect_check_host', log=False):
return
if len(self.playlist_data) == 1:
return

done_list = []
stop_sec_dict = prefetch_data['stop_sec_dict']
prefetch_data['on'] = True
while prefetch_data['on']:
for key, stop_sec in stop_sec_dict.copy().items():
ep = self.playlist_data.get(key)
if not ep:
continue
if not ep['media_path'].startswith('http'):
return
if not key or not stop_sec or key in done_list:
continue
if stop_sec / ep['total_sec'] < 0.5:
continue
# mix_sO 可能造成 index 重复
# next_ep = [e for e in self.playlist_data.values() if e['index'] == ep['index'] + 1]
# 字典目前保持插入顺序
# playlist_data 条目数量可能大于实际数量。(目前 mpv 不会)
list_playlist_data = list(self.playlist_data.values())
if ep == list_playlist_data[-1]:
break
next_ep_index = list_playlist_data.index(ep) + 1
next_ep_key = list(self.playlist_data.keys())[next_ep_index]
next_ep = list_playlist_data[next_ep_index]
try:
playlist = mpv.command('get_property', 'playlist')
except Exception:
logger.info('redirect_next_ep: mpv exception found, exit')
return
ne_url = next_ep['stream_url']
cu_url = ep['stream_url']
cu_re_url = self.data['stream_url'] if ep['file_path'] == self.data['file_path'] \
else ep.get('redirect_url', '')
cu_mpv_list = [i for i in playlist if i.get('playing')
and i['filename'] in (cu_url, cu_re_url)]
if not cu_mpv_list:
logger.info('redirect_next_ep: mpv cur playing filename not match playlist_data, may need check')
continue
cu_mpv_index = playlist.index(cu_mpv_list[0])
if cu_mpv_index == (len(playlist) - 1):
return
if playlist[cu_mpv_index + 1]['filename'] != ne_url:
logger.info('redirect_next_ep: mpv next filename not match playlist_data, may need check')
continue
mpv_cmd = next_ep['mpv_cmd']
ne_re_url = get_redirect_url(ne_url, follow_redirect=True)
mpv_cmd[1] = ne_re_url
try:
mpv.command(*mpv_cmd)
mpv.command('playlist-move', len(playlist), cu_mpv_index + 1)
mpv.command('playlist-remove', cu_mpv_index + 2)
except Exception:
logger.info('redirect_next_ep: mpv exception found, exit')
return
self.playlist_data[next_ep_key]['redirect_url'] = ne_re_url
# logger.info(f'redirect_next_ep: {next_ep_index=} {next_ep_key=}')
logger.info(f'redirect_next_ep: {mpv_cmd}')
done_list.append(key)
# pprint.pprint(mpv.command('get_property', 'playlist'))
time.sleep(5)

def prefetch_next_ep_loop(self):
prefetch_percent = configs.raw.getfloat('playlist', 'prefetch_percent', fallback=100)
prefetch_type = configs.raw.get('playlist', 'prefetch_type', fallback='null')
if prefetch_data['running'] or prefetch_percent == 100:
return
playlist_data = prefetch_data['playlist_data']
if len(playlist_data) == 1:
if len(self.playlist_data) == 1:
return
prefetch_data['running'] = True
prefetch_data['on'] = True
Expand All @@ -79,10 +146,11 @@ def prefetch_next_ep_loop():
prefetch_tuple = tuple(p.strip() for p in prefetch_tuple.split(',') if p.strip())
while prefetch_data['on']:
for key, stop_sec in stop_sec_dict.copy().items():
ep = playlist_data.get(key)
ep = self.playlist_data.get(key)
if not ep:
# logger.error(f'skip prefetch_data: {key=} {stop_sec=} not in playlist_data')
continue
if not ep['media_path'].startswith('http'):
return
if not key or not stop_sec or key in done_list:
continue
if prefetch_tuple and not ep['file_path'].startswith(prefetch_tuple):
Expand All @@ -93,10 +161,11 @@ def prefetch_next_ep_loop():
position = stop_sec / total_sec
if position * 100 <= prefetch_percent:
continue
next_ep = [e for e in playlist_data.values() if e['index'] == ep['index'] + 1]
if not next_ep:
list_playlist_data = list(self.playlist_data.values())
if ep == list_playlist_data[-1]:
break
ep = next_ep[0]
next_ep_index = list_playlist_data.index(ep) + 1
ep = list_playlist_data[next_ep_index]
if prefetch_type == 'sequence':
ep['gui_cmd'] = 'download_only'
requests_urllib('http://127.0.0.1:58000/pl', _json=ep)
Expand Down Expand Up @@ -146,6 +215,7 @@ def update_playlist_time_loop(self):
self.http_sub_auto_next_ep_time_loop(key_field=key_field_map[self.player_name])
else:
self.playlist_time = stop_sec_function_dict[self.player_name](stop_sec_only=False, **self.player_kwargs)
# 未兼容播放器多开,暂不处理
prefetch_data['on'] = False
prefetch_data['stop_sec_dict'].clear()

Expand Down Expand Up @@ -254,6 +324,7 @@ def playlist_add_mpv(mpv: MPV, data, eps_data=None, limit=10):
episodes = eps_data or list_episodes(data)
append = False
is_iina = getattr(mpv, 'is_iina')
mount_disk_mode = data['mount_disk_mode']
# 检查是否是新版loadfile命令
# https://github.com/mpv-player/mpv/commit/c678033
new_loadfile_cmd = False
Expand All @@ -269,9 +340,10 @@ def playlist_add_mpv(mpv: MPV, data, eps_data=None, limit=10):
for ep in episodes:
basename = ep['basename']
media_title = ep['media_title']
playlist_data[media_title] = ep
if is_iina:
if is_iina and mount_disk_mode:
playlist_data[basename] = ep
else:
playlist_data[media_title] = ep
if basename == data['basename']:
append = True
continue
Expand Down Expand Up @@ -318,10 +390,11 @@ def playlist_add_mpv(mpv: MPV, data, eps_data=None, limit=10):
try:
options = (f'title="{media_title}",force-media-title="{media_title}"'
f',osd-playing-msg="{media_title}",start=0{sub_cmd}{chap_cmd}')
if new_loadfile_cmd:
mpv.command('loadfile', ep['media_path'], 'append', '-1', options)
else:
mpv.command('loadfile', ep['media_path'], 'append', options)
mpv_cmd = ['loadfile', ep['media_path'], 'append', '-1', options]
if not new_loadfile_cmd:
del mpv_cmd[-2]
mpv.command(*mpv_cmd)
ep['mpv_cmd'] = mpv_cmd
except OSError:
logger.error('mpv exit: by playlist_add_mpv: except OSError')
return {}
Expand Down
13 changes: 13 additions & 0 deletions utils/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,18 @@ def main_ep_intro_time(main_ep_info):
return res


def show_version_info(extra_data):
py_script_version = '2024.03.27'
gm_info = extra_data.get('gmInfo')
user_agent = extra_data.get('userAgent')
if not gm_info:
_logger.info('userscript info not found, userscript update needed')
return
_logger.info(f"PyScript/{py_script_version} UserScript/{gm_info['script']['version']}"
f" {gm_info['scriptHandler']}/{gm_info['version']}")
_logger.info(user_agent)


def parse_received_data_emby(received_data):
extra_data = received_data['extraData']
main_ep_info = extra_data['mainEpInfo']
Expand Down Expand Up @@ -391,6 +403,7 @@ def parse_received_data_emby(received_data):
intro_end=intro_time.get('intro_end'),
server_version=server_version
)
show_version_info(extra_data=extra_data)
return result


Expand Down

0 comments on commit aee651f

Please sign in to comment.