Loading... # 前言 **本文使用的Emby版本为4.5.4** 承接某一篇文章:[Emby修改视频源为od或gd直链](https://weinb.top/index.php/archives/165/) 这是魔改某大佬的方法,但是博主却无法做到更完善,因为本人在JavaScript方面如同一个门外汉,无法解决网页端调用失败的问题,以及直链页面加密后获取直链失败的问题;此外,每次换小鸡后安装njs插件也是个麻烦的问题。 最终用Python撸了一个反代工具: 主要有以下特点: - [X] 解决网页端播放失败的问题 - [X] 支持设置密码的直链页面(目前仅支持Onemanager) - [X] 支持源码运行或二进制文件运行 - [X] 多文件夹多路径替换 - [X] 下载视频时走直链 # 网页端播放问题解决思路 与使用无关,不感兴趣的直接往下翻。在重定向过程中,刚开始得到的是类似 https://xxx.mkv 这样的文件,但是在使用这个直链的时候,会再次重定向(301)到一个新的微软的文件地址,是平时下载微软文件是那一长串有你微软账户域名和api新的的长串链接,而emby网页端只支持一次301重定向,所以需要在301时直接指向最终的那个链接。 # 使用 [](https://github.com/666wcy/emby-python) ## 方法一:打包文件直接使用 在[releases](https://github.com/666wcy/emby-python/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/" } ] ``` 修改后最好进行格式化检查看格式是否正确:[点击检验](https://www.huatools.com/json-dict/) 配置完成后,运行二进制文件(此处为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** ,也是[原方法](https://weinb.top/index.php/archives/165/)的作者,自己只是做了小部分修改(修改软件包名和路径替换部分)。 在`/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` 最终实现效果如下  最后修改:2021 年 09 月 08 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 0 如果觉得我的文章对你有用,请随意赞赏
11 条评论
不是emby版本的问题,我在windows是使用是没问题的,但放到服务器centos上有的视频就播不了,提示没有兼容的流
应该是版本的问题,我原来用的4.6.7的,换了4.5.4就没问题了。大佬太强了!
试了以下,mkv格式的基本都能播,mp4的有的能播,有的不行提示没有兼容的流,就算能播放也会提示没有兼容的流。不知道大佬有没有遇到这种情况。
试了播放不成功,"GET /socket?api_key=7259c52792af46d6897938c076d20b0e&deviceId=SmVsbHlmaW5NZWRpYVBsYXllciAxLjYuMSAod2luZG93cy14ODZfNjQgMTApfDE2NDM4OTMwNjM3ODQ1 HTTP/1.1" 400 -
可以写一个调用MPB的吗?
试了试没有成功,没有基础,只能期待有人出个保姆教程。
大佬,貌似od 世纪互联行不通?我反代之后新的反代端口用客户端登录不上,显示用户名密码错误;网页端虽然能登录上,但是在用户界面也无法增加新用户,非常奇怪,想请教您一下
我的配置是onemanager+od世纪互联
检查配置是否正确,或者查看程序是否输出什么错误
您有没有tg 或者邮箱可以私发我,我把输出给您看看
https://t.me/Ben_chao