Skip to content
Dylan-1107Public

About

100% AI 完成 · 把手机本地视频目录变成 Emby 服务器:任何 Emby 客户端可直连播放,支持续播、自动弹幕与刮削

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Embylite(原 LocalEmby)

Important

本项目全部由 AI 完成。 从技术选型、架构设计、代码编写、调试排错,到文档撰写与发版说明,全部由 AI 独立完成;人类只负责提出需求、提供测试设备、验收结果。 仓库里的每一行代码、每一份文档都出自 AI 之手,没有一行是人工手写的。 详见文末 关于 AI 参与。

AI release download minSdk

更名说明:本项目原名 LocalEmby(本地影院),自 2.6.0 起统一更名为 Embylite。 为不影响老用户升级,包名仍保留 com.local.embylite(Android 以包名识别应用,改包名会导致新旧版本无法覆盖安装、且已保存的设置与播放进度丢失),因此桌面图标名称显示为 Embylite,而系统内部标识依旧是 com.local.embylite。 GitHub 仓库与历史 Release 仍沿用原地址 github.com/Dylan-1107/LocalEmby,上方下载链接不受影响。

下载安装

直接下载(永久链接,始终指向最新版)

https://github.com/Dylan-1107/LocalEmby/releases/latest/download/LocalEmby.apk

历史版本见 Releases。

装好后打开 Embylite → 授权视频目录(选择文件夹或授予全部文件访问)→ 启动服务器。与手机同一 Wi-Fi 的设备,请在 Emby 客户端添加 App 显示的“局域网”地址(例如 http://192.168.1.23:8096);只有本机客户端使用 http://127.0.0.1:8096,用户名密码随意填。

界面

底部导航分为四项:首页、视频信息、日志、设置。

  • 首页:只保留服务器状态、访问地址、启停/复制/刷新、服务数值与运行指标
  • 视频信息:独立一页,固定头部(弹幕绑定 + 元数据刮削两组统计 + 筛选页签),下方是独立滚动的结果列表,逐文件查看与确认候选
  • 设置:媒体目录、服务端口、开机自启、Root 保活、弹幕源、TMDB 刮削
首页 弹幕识别
首页 弹幕
设置 请求日志
设置 日志

把手机里的本地视频目录,变成一个支持本机和局域网访问的 Emby 服务器。装好之后,本机客户端添加 http://127.0.0.1:8096,同一 Wi-Fi 的其他设备添加 App 显示的局域网地址,就能像浏览真 Emby 一样浏览、播放本地视频,并且播放进度会记忆、可续播,还支持自动匹配弹幕。

本项目是为了解决一个具体问题:想在 Emby 客户端里看手机本地视频,又不想为此专门跑一台 NAS / Jellyfin。它在手机本地模拟 Emby Server 的 HTTP API,零依赖、零转码,全部直连播放。

功能

  • HyperOS 风格界面:基于 Miuix(Compose Multiplatform UI 库)重写,卡片式布局、系统级观感;底部导航切换首页 / 弹幕 / 日志 / 设置,弹幕识别与设置各自独立成页

  • 本地目录即媒体库,两种授权方式:

    • 选择文件夹(SAF):逐个目录授权,可持久化、不需要危险权限;Android 11+ 系统不允许选存储根目录,所以只能选具体文件夹
    • 所有文件访问权限:在系统设置里一次性授予 MANAGE_EXTERNAL_STORAGE,之后直接扫全盘,无需选目录(本项目不上架 Play,不受其政策限制)
  • Emby API 模拟:实现登录、媒体库、条目列表/详情、PlaybackInfo、图片、视频流(HTTP Range 拖动)、播放进度上报等接口

  • 续播与进度记忆:通过 Emby 的 PlaybackPositionTicks 协议上报,客户端原生「继续观看」生效;多个客户端共享同一份进度

  • 剧集识别:结合父目录名与文件名解析「剧名 / 季 / 集」(SxxExx、第N集、EPxx、纯数字等),区分「明确季」「未分季」「特别篇」,并把多集合并、上下篇等文件标记为命名有歧义,不参与自动绑定

  • 剧集结构浏览:Emby 客户端里按「剧集 → 季 → 集」层级浏览(/shows/{id}/Episodes、/shows/nextup、/items),不再把所有文件摊平成电影

  • 自动弹幕:客户端请求 /api/danmu/{itemId} 时,按剧集身份(剧名 + 季 + 集)匹配弹弹play 兼容弹幕源,并对候选做二次校验,避免错绑同名剧集;绑定结果带指纹缓存(弹幕源 / 路径 / 大小 / 身份),文件或配置变化会自动失效重识

  • 识别结果可视化:「视频信息」页顶部为 HyperOS 设置风格分组卡片(开关 / 锚定下拉 / 进度条),展示弹幕绑定与元数据刮削的进度、已处理 / 待确认 / 失败统计,可按状态筛选;列表项展开可查看详情、确认候选、重新搜索(可改关键词)或清除结果——刮错了只纠错那一个文件

  • 增量识别与刮削:识别/刮削任务分三种模式——增量刷新(默认,只处理新增与失败重试)、重试失败与待确认、全量重建(需二次确认,人工锁定结果不覆盖);待确认/未命中条目保持安静,不再每次扫描反复请求;失败按指数退避重试;支持中途取消

  • 剧集级刮削聚合:同一部剧只做一次 TMDB 搜索 / 详情 / 海报下载,分集共享(请求量降 ~95%);一部剧确认候选后同剧其他待确认集自动继承;tvshow.nfo 与剧集海报每剧只写一次

  • 媒体库索引增量:扫描结果持久化索引,重启 / 刷新只 diff——新增/变化的文件才探测时长并入队,消失的文件自动清理状态;文件未变时秒出,不再全量重扫

  • 元数据刮削(TMDB):扫描后自动按文件名 / 剧名搜索 TMDB(zh-CN),唯一命中自动刮削,多候选进「待确认」人工选择;结果写入元数据覆盖层(标题、简介、年份、评分、演职人员、海报),并尽力生成 Kodi 兼容 NFO(<视频名>.nfo / tvshow.nfo / movie.nfo)与 poster.jpg 落盘到视频目录(SAF 或只读目录时自动回退应用私有目录);本地已有 NFO 的文件直接导入、不走网络;刮削状态带指纹缓存(Key / 路径 / 大小 / 身份),文件或配置变化自动重刮。TMDB Key 默认内置公共 Key 开箱即用,可在「设置 → 视频信息」替换为你自己的(themoviedb.org 免费申请)

  • 刮削元数据:支持通过 /api/dev/metadata 注入标题、简介、年份、评分、演职人员、海报

  • 后台常驻:前台服务 + WakeLock + 开机自启(仅在你主动开启过服务器时恢复)

  • Root 保活(可选):设备已 root 时,可在「设置」页开启;开启后每次启动服务器会用 su 把本应用加入电池优化白名单、解除后台限制并拉起前台服务,降低被 ROM 杀后台的概率。未 root 或拒绝授权时自动跳过,不影响正常使用;也可随时手动触发一次

  • 请求日志:App 内可查看全部请求(底部「日志」页),便于排查客户端兼容问题

目录结构

app/
  src/
    AndroidManifest.xml
    com/local/embylite/
      # ---- Compose UI(沿用原有卡片样式)----
      MainActivity.kt       # 导航壳 + 首页内容(HomeContent)+ 底部导航
      VideoInfoPage.kt      # 视频信息页:弹幕绑定 + 元数据刮削统计/筛选/候选确认
      SettingsPage.kt       # 设置页:媒体目录、端口、开机自启、Root 保活、弹幕源、TMDB 刮削
      LogActivity.kt        # 请求日志页

      # ---- 服务端逻辑(Java)----
      EmbyHttpServer.java   # 原生 HTTP 服务器:路由、Range 流媒体、弹幕/元数据接口
      EmbyApi.java          # Emby 协议层:System/Users/Items/PlaybackInfo/Sessions 等
      VideoLibrary.java     # 媒体库扫描(SAF / MediaStore / 目录遍历)
      Item.java             # 条目模型 + 文件名剧集解析(SxxExx、第N集、EPxx)
      EpisodeIdentity.java  # 剧集身份解析:父目录 + 文件名 → 剧名/季/集,标记歧义与特别篇
      EpisodeMatcher.java   # 弹幕剧集绑定:搜索、校验、缓存(带指纹)、进度
      EpisodeMatchPolicy.java # 候选校验策略(纯 Java,独立回归测试)
      ScrapeTitle.java      # 文件名 → 搜索标题 + 年份(纯 Java,独立回归测试)
      ScrapePolicy.java     # TMDB 候选接受策略(纯 Java,独立回归测试)
      NfoData/NfoWriter/NfoParser.java # Kodi 兼容 NFO 模型/生成/解析(纯 Java)
      TmdbClient.java       # TMDB API v3 客户端(搜索/详情/分集/海报)
      VideoInfoService.java # 元数据刮削编排:TMDB → 覆盖层 + NFO/海报落盘(带指纹)
      DanmuNetwork.java     # 弹幕源网络层(OkHttp + HTTPS DNS 兜底)
      ProgressDb.java       # 播放进度存储(Emby ticks)
      MetadataStore.java    # 刮削元数据覆盖层
      DanmuProxy.java       # 弹幕源代理(match + comment)
      ThumbProvider.java    # 视频抽帧缩略图 / 占位图
      RootKeepAlive.java    # Root 保活:su 白名单 + 解除后台限制(可选,需用户显式开启)
      HttpService.java      # 前台服务
      Settings.java         # 偏好设置
      BootReceiver.java     # 开机恢复
    res/                    # 布局与资源(strings.xml 中 app_name = Embylite)
  assets/web/index.html     # 浏览器调试页(列表 + 播放 + 进度上报)
tests/
  EpisodeRegression.java    # 剧集解析 / 候选校验回归测试(纯 Java,无需设备)
docs/                       # README 截图

构建

需要 JDK 17 与 Android SDK(compileSdk 36 / build-tools 36.0.0)。

# 1. 配置 SDK 路径
echo "sdk.dir=/path/to/android-sdk" > local.properties

# 2. 打包
./gradlew assembleRelease
# 产物:app/build/outputs/apk/release/app-release.apk

签名默认使用仓库根目录的 debug.keystore(signingConfigs.local),正式发布请替换为你自己的密钥。

技术栈:AGP 8.13.2 · Kotlin 2.3.20 · Compose Multiplatform 1.10.3 · Miuix 0.8.8 · minSdk 26 / targetSdk 36 · 版本 2.8.0(versionCode 13)。

纯 Java 回归测试

剧集解析与刮削核心(身份解析 / 标题净化 / 候选校验 / NFO 读写)都是纯 Java 逻辑,改完规则先跑回归,不需要设备或 Gradle:

# 剧集解析 / 弹幕候选校验
javac -encoding UTF-8 -d build/regress \
  app/src/com/local/embylite/EpisodeIdentity.java \
  app/src/com/local/embylite/EpisodeMatchPolicy.java \
  app/src/com/local/embylite/Item.java \
  tests/EpisodeRegression.java
java -cp build/regress com.local.embylite.EpisodeRegression
# 期望输出:PASS 38 episode identity and candidate regression cases

# 刮削核心(标题净化 / TMDB 策略 / NFO 读写)
javac -encoding UTF-8 -d build/scrape \
  app/src/com/local/embylite/EpisodeIdentity.java \
  app/src/com/local/embylite/ScrapeTitle.java \
  app/src/com/local/embylite/ScrapePolicy.java \
  app/src/com/local/embylite/NfoData.java \
  app/src/com/local/embylite/NfoWriter.java \
  app/src/com/local/embylite/NfoParser.java \
  tests/ScrapeRegression.java
java -cp build/scrape com.local.embylite.ScrapeRegression
# 期望输出:PASS 22 scrape regression cases

使用

  1. 安装 APK,打开「Embylite」
  2. 在首页点「视频目录」卡片,选择文件夹授权视频目录(也可授予「所有文件访问权限」直接扫全盘)
  3. 点「启动」,界面会显示本机地址和局域网地址(点「复制」会复制局域网地址)
  4. 在同一 Wi-Fi 的 Emby 客户端里添加服务器:填写界面显示的局域网地址,例如 http://192.168.1.23:8096;如果客户端就在手机本机,再填写 http://127.0.0.1:8096。用户名密码随意填
  5. 点底部「弹幕」进入弹幕识别页,可查看识别进度、逐文件绑定状态并确认候选
  6. 浏览播放即可,进度自动记忆

局域网内其他设备也可以访问 http://<手机IP>:8096(同 WiFi)。

弹幕

客户端会向服务器请求 /api/danmu/{itemId}?option=GetJsonById。服务端先建立「文件 → 弹幕剧集」绑定,再取该剧集的弹幕:

POST {base}/api/v2/match          {"fileName":"..."}  → matches[0].episodeId
GET  {base}/api/v2/comment/{id}  ?withRelated=true    → {count, comments:[{p,m}]}

绑定不是简单取 matches[0]:EpisodeMatchPolicy 会用本地解析出的「剧名 / 季 / 集」去校验每个候选的标题与集号,只有剧名归一化后一致、集号相同、季信息不冲突才接受。

  • 明确季的文件(S02E03、第二季第3集)必须有季证据才能绑定,避免第二季被绑到第一季
  • 未分季的文件只会绑定到同样未分季的候选;特别篇(S00)不会绑到正片
  • 命名有歧义的文件(S01E01-E02 合集、上下篇等)不参与自动绑定,需拆分后重识
  • 绑定结果按「弹幕源 + 路径 + 大小 + 解析身份」生成指纹缓存,任一变化即失效重识;「弹幕」页可查看进度、确认候选、重新识别

弹幕源地址在 Settings.getDanmuApi() 中配置(默认值见 DanmuProxy.DEFAULT_API,可自行替换为你部署的弹弹play 兼容服务)。网络层(DanmuNetwork)在系统 DNS 失败时走 DoH 兜底,同时保留原始主机名、SNI 与证书校验。

弹幕内容来自第三方服务,本项目不存储、不提供任何弹幕内容。

已实现的主要接口

接口 说明
GET /System/Info[/Public] 服务器信息
POST /Users/AuthenticateByName 登录(任意账号密码),返回 AuthenticationResult
GET /Users/{uid}/Views 媒体库视图
GET /Users/{uid}/Items 条目列表(分页、排序、搜索、ParentId 过滤)
GET /Users/{uid}/Items/Latest、/Items/Resume 最新 / 继续观看
GET /Users/{uid}/Items/{id} 条目详情(含 Series / Season 虚拟条目)
GET /Shows/{seriesId}/Episodes、/Shows/{seriesId}/Seasons 剧集 → 季 → 集层级浏览
GET /Shows/NextUp 下一集
POST /Items/{id}/PlaybackInfo 播放信息(直连播放,缺失时长时自动探测)
GET /Videos/{id}/stream 视频流,支持 HTTP Range
GET /Items/{id}/Images/Primary 缩略图 / 刮削海报(Series / Season 复用代表帧)
`POST /Sessions/Playing[/Progress /Stopped]`
GET /api/danmu/{id} 弹幕代理(按剧集绑定)
GET /api/episodes/status 剧集识别进度与每个文件的绑定状态
POST /api/dev/metadata 注入刮削元数据
GET /api/dev/log 请求日志

已知限制

  • 不做转码:只提供直连播放,客户端需自行支持文件编码(Hills / YAmby 内置 mpv,支持良好)
  • 无用户体系:任意账号密码都能登录,仅建议本机或可信局域网使用
  • 无 WebSocket:部分客户端的实时会话推送不可用,不影响播放
  • APK 体积约 7MB:Compose 运行时占用较大
  • MediaStreams 为占位信息:部分客户端会显示固定的 1080p H.264 / AAC,实际轨道由播放器自行探测
  • 小米等 ROM 对后台限制较严,若服务器被杀,可在系统设置里允许自启动并锁定后台

关于 AI 参与

这个项目全部由 AI 完成。

Embylite 不是一个"AI 辅助写了几段代码"的项目,而是从零到发版全程由 AI 驱动:需求由人类提出,其余全部由 AI 落地。

环节 由谁完成
提出需求、决定功能取舍、最终验收 人类(项目所有者)
技术选型与架构设计 AI
全部 Java / Kotlin 代码编写 AI
调试排错、真机联调(adb / logcat / 截图逐帧比对) AI
剧集解析、刮削策略的回归测试用例 AI
README、交接文档、Release 说明 AI

人类在这个项目里不写代码,只做三件事:说清楚想要什么、提供一台真机供验证、对结果说"行"或"不行"。所有代码、所有文档、所有排查过程都出自 AI。

这样做的好处是迭代速度:一个功能从"提需求"到"真机验证通过"通常在一次对话内完成。代价是每个结论都必须实测——所以仓库里才会有一堆回归测试和 docs/BUILD-HANDOFF.md 这样的交接文档,让下一个人(或下一个 AI)能独立接续。

数据来源与致谢

  • 弹幕:弹幕通过弹弹play 兼容接口获取,服务端基于开源项目 danmu_api(作者 huangxd-)自部署,仓库内置的默认弹幕源即该项目的一个部署实例。弹幕内容版权归原平台及发布者所有。
  • 元数据刮削:影视元数据(标题、简介、评分、演职人员、海报等)来自 TMDB 的公开 API。This product uses the TMDB API but is not endorsed or certified by TMDB.

免责声明

本项目仅用于管理和播放你自己拥有的本地媒体文件,不提供、不索引、不托管任何影视内容。刮削与弹幕数据来自第三方公开服务,请遵守相应服务条款与当地法律法规。

About

100% AI 完成 · 把手机本地视频目录变成 Emby 服务器:任何 Emby 客户端可直连播放,支持续播、自动弹幕与刮削

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages