前言
本文使用的Emby版本为4.5.4
这是魔改某大佬的方法,但是博主却无法做到更完善,因为本人在JavaScript方面如同一个门外汉,无法解决网页端调用失败的问题,以及直链页面加密后获取直链失败的问题;此外,每次换小鸡后安装njs插件也是个麻烦的问题。
最终用Python撸了一个反代工具:
主要有以下特点:
- 解决网页端播放失败的问题
- 支持设置密码的直链页面(目前仅支持Onemanager)
- 支持源码运行或二进制文件运行
- 多文件夹多路径替换
- 下载视频时走直链
网页端播放问题解决思路
与使用无关,不感兴趣的直接往下翻。在重定向过程中,刚开始得到的是类似 https://xxx.mkv 这样的文件,但是在使用这个直链的时候,会再次重定向(301)到一个新的微软的文件地址,是平时下载微软文件是那一长串有你微软账户域名和api新的的长串链接,而emby网页端只支持一次301重定向,所以需要在301时直接指向最终的那个链接。
使用
方法一:打包文件直接使用
在releases下载最新的打包文件,上传到服务器
在同目录新建config.json文件,编辑如下内容
{
"main_site": "http://127.0.0.1:8096/",
"main_port": "8096",
"new_port": "2333",
"api_key": "1fb2477b5cb14d32843753xxxxxxxx",
"redirects": "True",
"password_key": "True",
"password_value": "xxxx",
"replace_list": [
{
"from": "/media/video/",
"to": "http://xxxx.weinb.top/shi/"
},
{
"from": "/media/adult/",
"to": "http://xxxx.weinb.top/adult/emby/"
}
]
}
配置解释:
main_site
:emby的原本地址,后面需要加/
main_port
:emby原端口
new_port
:反代后的端口
api_key
:emby的api,需要自己在api中设置
redirects
:是否需要获取直链后再次获取真实文件链接,od需要开启此项,True为开启,False为关闭
password_key
:直链的目录界面是否设置有密码,目前仅支持Onemanager
password_value
:密码的值,开启前一项此项才生效,关闭时可留空或任意值
replace_list
:替换的规则此处效果如下
/media/video/newanime/奇巧计程车/Season 01/[Lilith-Raws] Odd Taxi - S01E01 [Baha][WEB-DL][1080p][AVC AAC][CHT][MP4].mp4
上边路径的文件会被替换为
http://xxxx.weinb.top/shi/newanime/奇巧计程车/Season 01/[Lilith-Raws] Odd Taxi - S01E01 [Baha][WEB-DL][1080p][AVC AAC][CHT][MP4].mp4
支持无限条规则,注意格式即可,例如三条规则的格式
"replace_list": [
{
"from": "/media/video/",
"to": "http://xxxx.weinb.top/shi/"
},
{
"from": "/media/adult/",
"to": "http://xxxx.weinb.top/adult/emby/"
},
{
"from": "/media/test/",
"to": "http://xxxx.weinb.top/test/emby/"
}
]
修改后最好进行格式化检查看格式是否正确:点击检验
配置完成后,运行二进制文件(此处为Linux环境)
chmod +x web & ./web
即可完成,注意,此方法运行断开SSH连接后程序也会停止,请配置Screen之类的程序保持运行
方法二:运行源码
环境要求:Python3.6+
按方法一的要求设置config.json文件
安装依赖
pip3 install flask
pip3 install requests
获取源码
wget https://raw.githubusercontent.com/666wcy/emby-python/main/web.py
运行程序
python3 web.py
此方法默认你拥有部分Python基础,不做过多赘述。
增加功能(网页版)
在网页版原本功能上加入调用外部播放器,此方法来自Tg大佬 @baipiaoking ,也是原方法的作者,自己只是做了小部分修改(修改软件包名和路径替换部分)。
在/opt/emby-server/system/dashboard-ui
路径找到index.html
编辑index.html
文件,找到以下代码
<script src="apploader.js" defer></script>
在此行代码下添加如下代码
<script type="text/javascript" src="./externalPlayer.js"></script>
保存后退出。在同文件夹下新建externalPlayer.js
文件,添加如下内容
const api_key = "1fb2477b5cb14d32843753977a60cb17";
const reg = /\/[a-z]{2,}\/\S*?id=/;
let timer = setInterval(function() {
let potplayer = document.querySelectorAll("div[is='emby-scroller']:not(.hide) .potplayer")[0];
if(!potplayer){
let mainDetailButtons = document.querySelectorAll("div[is='emby-scroller']:not(.hide) .mainDetailButtons")[0];
if(mainDetailButtons){
let buttonhtml = `<div class ="flex">
<button id="cloudPot" type="button" class="detailButton emby-button potplayer" title="Cloud"> <div class="detailButton-content"> <i class="md-icon detailButton-icon"></i> <div class="detailButton-text">PotPlayer</div> </div> </button>
<button id="embyIINA" type="button" class="detailButton emby-button iina" title="IINA"> <div class="detailButton-content"> <i class="md-icon detailButton-icon"></i> <div class="detailButton-text">IINA</div> </div> </button>
<button id="nPlayer" type="button" class="detailButton emby-button nPlayer" title="nPlayer"> <div class="detailButton-content"> <i class="md-icon detailButton-icon"></i> <div class="detailButton-text">NPlayer</div> </div> </button>
<button id="mxPlayer" type="button" class="detailButton emby-button mxPlayer" title="MxPlayer"> <div class="detailButton-content"> <i class="md-icon detailButton-icon"></i> <div class="detailButton-text">MXPlayer Pro</div> </div> </button>
<button id="mxPlayerad" type="button" class="detailButton emby-button mxPlayerad" title="MxPlayerad"> <div class="detailButton-content"> <i class="md-icon detailButton-icon"></i> <div class="detailButton-text">MXPlayer Free</div> </div> </button>
</div>`
mainDetailButtons.insertAdjacentHTML('afterend', buttonhtml)
document.querySelector("div[is='emby-scroller']:not(.hide) #cloudPot").onclick = cloudPot;
document.querySelector("div[is='emby-scroller']:not(.hide) #embyIINA").onclick = embyIINA;
//document.querySelector("div[is='emby-scroller']:not(.hide) #mxPlayer").onclick = mxPlayer;
document.querySelector("div[is='emby-scroller']:not(.hide) #nPlayer").onclick = nPlayer;
document.querySelector("div[is='emby-scroller']:not(.hide) #mxPlayer").onclick = mxPlayer;
document.querySelector("div[is='emby-scroller']:not(.hide) #mxPlayerad").onclick = mxPlayerad;
}
}
}, 1000)
async function getItemInfo(){
let itemInfoUrl = window.location.href.replace(reg, "/emby/Items/").split('&')[0] + "/PlaybackInfo?api_key=" + api_key;
console.log("itemInfo:" + itemInfoUrl);
let response = await fetch(itemInfoUrl);
if(response.ok) {
return await response.json();
}else{
alert("获取视频信息失败,检查api_key是否设置正确 "+response.status+" "+response.statusText);
throw new Error(response.statusText);
}
}
function getSeek(){
//.resumeButtonText
let resumeButton = document.querySelector(".resumeButtonText");
let seek = '';
let temp = `播放时间信息:${resumeButton.innerText}`;
console.log(temp);
if (resumeButton) {
if (resumeButton.innerText.includes('恢复播放')) {
seek = resumeButton.innerText.replace(' 恢复播放', '');
seek = seek.replace('从 ', '');
}
} return seek;
}
function getSubUrl(itemInfo, MediaSourceIndex){
let selectSubtitles = document.querySelector("div[is='emby-scroller']:not(.hide) select.selectSubtitles");
let subTitleUrl = '';
if (selectSubtitles) {
if (selectSubtitles.value > 0) {
if (itemInfo.MediaSources[MediaSourceIndex].MediaStreams[selectSubtitles.value].IsExternal) {
let subtitleCodec = itemInfo.MediaSources[MediaSourceIndex].MediaStreams[selectSubtitles.value].Codec;
let MediaSourceId = itemInfo.MediaSources[MediaSourceIndex].Id;
let domain = window.location.href.replace(reg, "/emby/videos/").split('&')[0];
subTitleUrl = `${domain}/${MediaSourceId}/Subtitles/${selectSubtitles.value}/${MediaSourceIndex}/Stream.${subtitleCodec}?api_key=${api_key}`;
console.log(subTitleUrl);
}
}
} return subTitleUrl;
}
async function getCloudSubUrl(itemInfo, MediaSourceIndex){
let selectSubtitles = document.querySelector("div[is='emby-scroller']:not(.hide) select.selectSubtitles");
let subTitleUrl = '';
if (selectSubtitles) {
if (selectSubtitles.value > 0) {
if (itemInfo.MediaSources[MediaSourceIndex].MediaStreams[selectSubtitles.value].IsExternal) {
let embySubPath = itemInfo.MediaSources[MediaSourceIndex].MediaStreams[selectSubtitles.value].Path;
subTitleUrl = await url2webdrive(embySubPath);
}
}
}
return subTitleUrl;
}
//emby路径转换为网盘路径
async function url2webdrive(embyVideoPath){
if(embyVideoPath.includes("/media/video")){
return embyVideoPath.replace("/media/video", "http://xxx.weinb.top/shi");
}
if(embyVideoPath.includes("/media/adult")){
return embyVideoPath.replace("/media/adult", "http://xxx.weinb.top/adult/emby");
}
}
async function getCloudMediaUrl(){
let selectSource = document.querySelector("div[is='emby-scroller']:not(.hide) select.selectSource");
//let selectAudio = document.querySelector("div[is='emby-scroller']:not(.hide) select.selectAudio");
let itemInfo = await getItemInfo();
let MediaSourceIndex = 0;
for(let i = 0; i< itemInfo.MediaSources.length; i++){
if(itemInfo.MediaSources[i].Id == selectSource.value){ MediaSourceIndex = i;
};
}
let embyVideoPath = itemInfo.MediaSources[MediaSourceIndex].Path;
let cloudVideoUrl = await url2webdrive(embyVideoPath);
let subUrl = await getSubUrl(itemInfo, MediaSourceIndex);
console.log(cloudVideoUrl, subUrl);
return Array(cloudVideoUrl,subUrl);
}
async function cloudPot(){
let CloudMediaUrl = await getCloudMediaUrl();
let poturl = `potplayer://${encodeURI(CloudMediaUrl[0])} /sub=${encodeURI(CloudMediaUrl[1])} /current /seek=${getSeek()}`;
console.log(poturl);
window.open(poturl, "_blank");
}
async function mxPlayer(){
let CloudMediaUrl = await getCloudMediaUrl();
let poturl = `intent:${encodeURI(CloudMediaUrl[0])}#Intent;package=com.mxtech.videoplayer.pro;S.title=undefined;S.subs=${encodeURI(CloudMediaUrl[1])};end`;
console.log(poturl);
window.open(poturl, "_blank");
}
async function mxPlayerad(){
let CloudMediaUrl = await getCloudMediaUrl();
let poturl = `intent:${encodeURI(CloudMediaUrl[0])}#Intent;package=com.mxtech.videoplayer.ad;S.title=undefined;end`;
console.log(poturl); window.open(poturl, "_blank");
}
async function nPlayer(){
let CloudMediaUrl = await getCloudMediaUrl();
let poturl = `nplayer-${encodeURI(CloudMediaUrl[0])}`;
console.log(poturl);
window.open(poturl, "_blank");
}
async function embyIINA(){
let mediaUrl = await getCloudMediaUrl();
let iinaUrl = `iina://weblink?url=${escape(mediaUrl[0])}&new_window=1`;
console.log(`iinaUrl= ${iinaUrl}`);
window.open(iinaUrl, "_blank");
}
修改第一行的api为自己的api
修改约91行的url2webdrive
函数中的路径为自己的路径规则
保存后退出,主要检查js的文件访问权限。
重启Emby
最终实现效果如下