# go-emby2openlist **Repository Path**: arongwang_admin/go-emby2openlist ## Basic Information - **Project Name**: go-emby2openlist - **Description**: 代理emby上openlist的网盘请求到直链 - **Primary Language**: Go - **License**: GPL-3.0 - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2026-06-30 - **Last Updated**: 2026-06-30 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README
logo

go-emby2openlist

Go 语言编写的 Emby + OpenList 网盘直链反向代理,深度适配阿里云盘转码播放。

version build stars license
--- ## 目录 - [关于本仓库](#关于本仓库) - [本仓库更新说明](#本仓库更新说明) - [工作原理](#工作原理) - [功能特性](#功能特性) - [管理控制台](#管理控制台) - [客户端兼容性](#客户端兼容性) - [环境准备](#环境准备) - [部署安装](#部署安装) - [SSL 配置](#ssl-配置) - [自定义 Web 脚本与样式](#自定义-web-脚本与样式) - [OpenList 本地目录树](#openlist-本地目录树) - [开放 API](#开放-api) - [后续计划](#后续计划) --- ## 关于本仓库 本仓库基于上游项目 [AmbitiousJun/go-emby2openlist](https://github.com/AmbitiousJun/go-emby2openlist) **v2.8.0** 维护,在保留原有功能的基础上,针对高并发播放与目录树同步场景做了性能优化和稳定性修复。 | 项目 | 地址 | | --- | --- | | 上游项目 | https://github.com/AmbitiousJun/go-emby2openlist | | 本仓库 (GitHub) | https://github.com/ArongWang/go-emby2openlist | | 本仓库 (Gitee) | https://gitee.com/arongwang_admin/go-emby2openlist | > 若你需要功能更完善的通用方案,可参考 [bpking1/embyExternalUrl](https://github.com/bpking1/embyExternalUrl)。 --- ## 本仓库更新说明 相比上游 v2.8.0,本仓库在高并发播放、混合媒体库、目录树同步等场景下做了针对性优化。以下按模块说明实现细节与预期效果。 ### 一、缓存体系优化 #### 1. PlaybackInfo 路径缓存 播放直链前需先向 Emby 查询媒体本地路径(PlaybackInfo)。多客户端同时点播时,同一媒体会被重复请求,造成 Emby 源站压力。 | 项目 | 说明 | | --- | --- | | 缓存 Key | `itemId` + `MediaSourceId` + `api_key` 组合 | | TTL | 10 分钟 | | 并发去重 | 使用 `singleflight`,同一 Key 的并发请求只回源一次 | | 容量上限 | 16,384 条,超出后清理过期条目 | | 生效位置 | 直链重定向(`/stream`、`/original`)等流程 | #### 2. 302 直链重定向轻量缓存 网盘直链获取通常返回 302 跳转,响应体为空。若与 API 响应共用 body 缓存,会挤占配额且收益有限。 - 独立维护最多 **16,384** 条 302 缓存,仅保存 `Location`,不占用 body 缓存配额(总上限 100MB / 8092 条) - 命中时直接重定向,跳过 OpenList 重复请求 - 容量满时按过期时间批量淘汰至上限的 90%,降低频繁扫描开销 - Strm 远程地址重定向默认缓存 10 分钟 #### 3. 异步 body 缓存写入 上游在请求 goroutine 中同步写入缓存,大响应会阻塞后续处理。 | 项目 | 说明 | | --- | --- | | Worker 数量 | 4 个固定协程 | | 任务队列 | 512 条,非阻塞入队 | | 队列满时 | 丢弃最旧任务后重试;仍失败则放弃写入 | | 设计原则 | **永不阻塞请求 goroutine**,仅影响命中率,不影响正确性 | #### 4. 流式响应跳过 body 缓存 本地媒体库、Emby Web 静态资源等经 `ProxyOrigin` 回源时,响应体可能很大(视频流、JS/CSS 等)。若全部缓冲进内存,高并发下易 OOM。 - 响应体超过 **16MB** 时停止缓冲,标记 `skipCache` - 响应头 `Expired: -1` 的透传请求(交还 Emby 原逻辑)不缓冲 body - 不在 OpenList 代理范围内的媒体走 `passThroughToEmby`,同样不参与缓存 --- ### 二、代理路由精确化 #### 问题背景 上游对所有媒体路径统一尝试 OpenList 直链代理。当 Emby 媒体库同时包含**网盘挂载路径**与**本地/Web 资源**时,Web 静态文件、本地库媒体会被误走代理逻辑,导致播放异常或多余 OpenList 请求。 #### 解决方案:`NeedsOpenlistProxy` 新增统一判断函数,只有以下路径才走 OpenList 直链: | 条件 | 处理方式 | | --- | --- | | `local-media-roots` 配置的本地媒体 | 交还 Emby 原逻辑 | | 远程 Strm 地址 | 按 Strm 规则重定向 | | 不在 `mount-path` 且不在 `path.emby2openlist` 映射范围内 | 交还 Emby 原逻辑 | | 命中 `mount-path` 或 `emby2openlist` 前缀 | 走 OpenList 直链 | **路径前缀匹配**使用 `HasPathPrefix`(分隔符感知),例如 `/data/local` 不会误匹配 `/data/local-cloud`。 **交还 Emby 的处理方式:** - `/stream`、`/universal` 请求 → 302 重定向到 `/original`,由 Emby 原生链路播放 - 其他请求 → 设置 `Expired: -1` 后 `ProxyOrigin` 透传回源 上述逻辑已应用于 `PlaybackInfo` 改写、`Redirect2OpenlistLink`、`ProxyOriginalResource` 等入口。 --- ### 三、OpenList API 与目录树同步调度 #### 1. 客户端 API 优先级计数 目录树同步需要大量调用 OpenList `fs/list`、`fs/get` 等接口,与客户端播放争用同一 OpenList 实例,高峰期会拖慢起播。 | 项目 | 说明 | | --- | --- | | 计入优先级的请求 | 仅 `fs/list`、`fs/get`、`fs/other`(客户端播放相关) | | 不计入的请求 | Emby 回源、静态资源、PlaybackInfo 代理等普通 HTTP | | 繁忙阈值 | 并发 ≥ **3** 时判定为繁忙 | | Walk 行为 | 繁忙时暂停目录树分页遍历,降至阈值以下后继续 | #### 2. 相对空闲窗口同步 不要求 OpenList 完全零请求,而是等待连续 **5 秒**内 API 并发低于阈值,即启动/继续目录树同步。这样在持续有零星播放的服务器上,同步仍能推进。 定时同步(`refresh-interval`)每次触发前同样等待相对空闲。 #### 3. 目录树 Walk 容错 | 场景 | 上游行为 | 本仓库行为 | | --- | --- | --- | | InitSnapshot 本地磁盘 Walk 单路径失败 | 可能直接退出进程 | 记录日志并 `SkipDir`,继续扫描 | | OpenList 某子目录 list 失败 | 同步中断 | `markSkippedDir` 标记,跳过该目录继续 | | 本地目录创建失败 | — | 跳过并标记,避免误删已有缓存 | Walk 过程中每遍历 **128** 个本地路径会检查 OpenList 负载,繁忙时主动让路。 --- ### 四、稳定性修复 #### HTTP 重定向连接池泄漏 跟随 302 链时,中间响应的 body 未读取会导致 TCP 连接无法归还连接池。现已在每次跳转前用 `io.Copy(Discard)` 排空 body 再关闭。 #### m3u8 预处理通道 WaitGroup 转码 playlist 异步预热通道满时会淘汰队首任务,但未处理的被淘汰任务未调用 `Done()`,导致 `WaitGroup` 永久阻塞。淘汰时已补充 `Done()` 补偿。 --- ### 五、优化效果概览 | 场景 | 预期改善 | | --- | --- | | 多客户端同时播放同一资源 | PlaybackInfo 回源次数显著减少 | | 频繁拖动进度 / 切换清晰度 | 302 直链缓存减少 OpenList 重复请求 | | 混合本地库 + 网盘库 | 本地/Web 资源不再误走 OpenList 代理 | | 刮库 + 播放同时进行 | 播放优先,目录树在空闲窗口推进 | | 大文件本地库回源播放 | 不再缓冲超大响应体,内存更稳定 | | OpenList 个别目录临时不可用 | 同步跳过失败目录,服务不中断 | --- ### 继承自 v2.8.0 的功能 - **ge2o Web 管理控制台**:配置查看、日志实时查看、目录树手动更新 - Emby 首页快捷入口按钮(可在 `ge2o.web.disable-emby-btn` 关闭) --- ## 工作原理 ### 传统挂载模式 ``` 客户端 → Emby → 磁盘挂载 (rclone/cd2) → OpenList → 网盘 ``` 视频数据经服务器中转,播放速度受限于服务器上行带宽与性能。 ### 直链反代模式 ``` API 请求: 客户端 → 反代 → Emby 源服务器 → 返回(可缓存) 视频播放: 客户端 → 反代 → OpenList → 网盘直链 → 客户端直连网盘 ``` 播放阶段客户端直接从网盘拉流,不再消耗服务器流量;加载速度取决于网盘会员与客户端性能。 --- ## 功能特性 - OpenList 网盘原画 / Strm 直链播放 - 阿里云盘转码直链播放(不消耗三方流量包) - OpenList 本地目录树生成(Strm / 虚拟文件 / 音乐虚拟文件) - WebSocket 代理、客户端防转码(转容器) - 缓存中间件(大接口、直链、字幕) - 自定义 Web JS / CSS 注入 - ge2o Web 管理控制台 **转码直链说明** | 项目 | 说明 | | --- | --- | | 三方流量包 | 不消耗 | | 非会员限速 | 需自行测试 | | 多音轨 | 仅默认音轨 | | 内封字幕 | 会丢失;转码字幕会写入 PlaybackInfo | ![转码示例](assets/2024-08-31-17-15-53.jpg) **字幕缓存说明** - 字幕缓存固定 30 天 - 首次播放带字幕视频时,Emby 可能从本地挂载文件提取字幕(较慢,消耗服务器流量) - 建议使用第三方播放器(如 MX Player、Fileball)可规避此问题 **缓存时间** | 类型 | 时长 | | --- | --- | | 直链 | 10 分钟(兼容阿里云盘) | | 字幕 | 30 天 | | PlaybackInfo(转码) | 12 小时 | --- ## 管理控制台 v2.8.0 起内置 ge2o Web 管理台,部署后访问 `http://服务器IP:8095` 即可使用。 | 页面 | 功能 | | --- | --- | | 首页 | 快速导航、项目信息 | | 日志 | WebSocket 实时查看服务日志 | | 目录树 API | 可视化调用本地目录树更新接口 | | 设置 | 查看当前配置 | **相关配置**(`config.yml` → `ge2o`): ```yaml ge2o: api-secret: my-secret # 开放 API 密钥,必填 web: disable: false # 设为 true 禁用 Web 管理台 disable-emby-btn: false # 设为 true 隐藏 Emby 首页快捷按钮 ``` --- ## 客户端兼容性 | 客户端 | 版本 | 原画 | 阿里转码 | 备注 | | --- | --- | --- | --- | --- | | Gemby | v2.6.4 | ✅ | ✅ | — | | Emby Web | 4.8.8.0 | ✅ | ✅ | 转码字幕偶发挂载失败 | | Emby for Android | 3.4.23 | ✅ | ✅ | — | | Emby for AndroidTV | 2.0.95g | ✅ | ✅ | 调进度可能触发限频;无法挂载字幕 | | Fileball | — | ✅ | ✅ | — | | Infuse | — | ✅ | ❌ | 缓存设为「不缓存」可避免限频 | | VidHub | ≤ 1.0.7 | ✅ | ✅ | — | | Stream Music | 1.3.8 | ✅ | — | — | | Emby for Kodi Next Gen | 11.1.13 | ✅ | ✅ | 需开启 prores 转码;无法挂载字幕 | | Emby for iOS / macOS | — | ❓ | ❓ | 未充分测试 | --- ## 环境准备 1. 已有 Emby、OpenList 服务 2. Emby 媒体库路径与 OpenList 挂载路径可对应(前缀不一致可通过 `path.emby2openlist` 映射) 3. 中间挂载服务将网盘挂载到本地磁盘(推荐 [CloudDrive2](https://www.clouddrive2.com/) 或 [rclone](https://rclone.org/)) - 阿里云盘推荐 CD2,缓存大小设为极小值(如 1MB)以减少刮削流量 - ⚠️ 不建议通过 OpenList WebDAV 挂载,Token 失效会导致 Emby 元数据丢失 4. 已安装 Docker(源码构建还需 Git) --- ## 部署安装 ```shell git clone https://github.com/ArongWang/go-emby2openlist.git cd go-emby2openlist cp config-example.yml config.yml # 编辑 config.yml,参照下方路径映射示例 docker-compose up -d --build ``` 访问 `http://服务器IP:8095` 验证服务。自定义端口时修改 `docker-compose.yml` 中的端口映射即可。 ![路径映射示例](assets/2024-09-05-17-20-23.png) **常用命令** ```shell # 查看日志 docker logs -f go-emby2openlist -n 1000 # 修改配置后重启 docker-compose restart # 更新版本 docker-compose down && git pull && docker-compose up -d --build # 清理旧镜像 docker image prune -f ``` > 首次部署可参考[核心配置说明](https://github.com/AmbitiousJun/go-emby2openlist/issues/108#issuecomment-2928599051)先跑通,再按需补充其他项。更新后请对比 `config-example.yml` 是否有新增配置。 --- ## SSL 配置 1. 将证书与私钥放入 `ssl/` 目录 2. 在 `config.yml` 的 `ssl` 节配置文件名 容器内端口固定:HTTP `8095`,HTTPS `8094`。自定义对外端口时在 `docker-compose.yml` 中修改映射即可。 --- ## 自定义 Web 脚本与样式 | 类型 | 目录 | 格式 | | --- | --- | --- | | JS 脚本 | `custom-js/` | `.js` 文件,或写入远程 URL | | CSS 样式 | `custom-css/` | `.css` 文件,或写入远程 URL | 放入文件后重启服务生效。多文件请统一使用 UTF-8 编码。 **常用脚本示例** | 功能 | 链接 | | --- | --- | | 外部播放器按钮 | [ExternalPlayers.js](https://emby-external-url.7o7o.cc/embyWebAddExternalUrl/embyLaunchPotplayer.js) | | 首页轮播图 | [emby-swiper.js](https://raw.githubusercontent.com/newday-life/emby-web-mod/refs/heads/main/emby-swiper/emby-swiper.js) | | 键盘音量控制 | [audio-keyboard.js](https://github.com/AmbitiousJun/emby-css-js/blob/main/custom-js/audio-keyboard.js) | --- ## OpenList 本地目录树 监控 OpenList 目录变更,在本地生成对应目录结构供 Emby 扫描入库,支持 Strm、虚拟文件、音乐虚拟文件三种模式。 > ⚠️ 开启 ffmpeg 元数据提取存在风控风险,请谨慎使用。 ### 快速启用 1. 配置 `openlist.local-tree-gen`(见 `config-example.yml`) 2. 映射容器目录 `/app/openlist-local-tree` 和 `/app/lib` 到宿主机 3. 将宿主机目录树路径挂载到 Emby 容器并扫描入库 ### 三种生成模式 **1. Strm 文件** — 速度快,无 ffmpeg ```yaml openlist: local-tree-gen: enable: true strm-containers: mp4,mkv,mp3,flac ``` **2. 虚拟文件** — 生成同名空文件,默认时长 3 小时 ```yaml openlist: local-tree-gen: enable: true virtual-containers: mp4,mkv ffmpeg-enable: true # 可选,解析真实时长(有风控风险) ``` **3. 音乐虚拟文件** — 提取标签与时长,必须开启 ffmpeg ```yaml openlist: local-tree-gen: enable: true ffmpeg-enable: true music-containers: mp3,flac ``` ### 其他配置项 | 配置项 | 说明 | 示例 | | --- | --- | --- | | `auto-remove-max-count` | 批量删除上限,建议为总文件数的 3/4 | `6000` | | `refresh-interval` | 刷新间隔(分钟) | `60` | | `scan-prefixes` | 扫描前缀,空则全量 | `/电影` | | `allow-containers` | 容器白名单 | `ass,srt,sub` | > ffmpeg 不包含在 Docker 镜像中。首次开启 `ffmpeg-enable` 后程序会自动下载,请勿中断容器。 --- ## 开放 API ### 手动更新本地目录树 ``` POST /ge2o/openlist/local_tree/update Content-Type: application/json ``` **请求体** ```json { "secret": "my-secret", "prefix": "/音乐1/陈楚生", "refresh": false } ``` | 参数 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | `secret` | string | ✅ | 与 `ge2o.api-secret` 一致 | | `prefix` | string | | 路径前缀,须为完整子目录(如 `/A/B (2026)` 而非 `/A/B`) | | `refresh` | boolean | | 是否强制刷新 OpenList 数据,默认 `false` | **响应** HTTP 200,通过 body 判断结果: ```json { "success": false, "message": "密钥错误" } ``` 也可在 Web 管理台的「目录树 API」页面可视化调用。 --- ## 后续计划 | 计划 | 说明 | | --- | --- | | 多 OpenList 网盘支持 | 当前仅支持配置单个 OpenList 实例。后续计划支持同时接入多个 OpenList 服务(如阿里云盘、115、夸克等不同网盘),按路径前缀或媒体库自动路由到对应实例,便于混合挂载场景统一管理 | --- ## Star History Star History Chart