技術細節與開發 - MiCat-S/Mi302 GitHub Wiki

繁體中文 | 简体中文 | English

這一頁給想了解內部運作或參與開發的人:請求流程、路徑怎麼比對、已實作的 Emby 端點、管理 API、程式結構、怎麼跑測試,以及參考過的專案。

請求流程

Mi302 是一個 FastAPI 程式,用 uvicorn 執行,資料存在 SQLite(data/library.db)。

  1. 掃描:把媒體庫資料夾裡的 .strm、一般影片檔、nfo、圖片寫進資料庫,見 媒體庫與掃描。
  2. 瀏覽:播放器呼叫 Emby API(/Users/{id}/Views、/Items…),Mi302 從資料庫組出 Emby 的 BaseItemDto。
  3. 播放資訊:POST /Items/{id}/PlaybackInfo。strm 項目回 Protocol=Http、IsRemote=true,只允許直接播放(關閉轉碼),DirectStreamUrl 指回 /videos/{id}/stream.{格式}。
  4. 串流:播放器請求 /Videos/{id}/stream、/Videos/{id}/original.xxx 或 /Items/{id}/Download:
    • strm 項目:讀 strm 內容,套用路徑替換;帶 pickcode 就用播放器的 User-Agent 向 115 取直鏈,否則可以先跟完上游的重導向;最後回 302 Location: <真實網址>。
    • 一般影片檔:直接送檔,支援 Range。
  5. 進度:/Sessions/Playing… 記續播位置和已看,也拿來學片頭片尾,見 片頭片尾跳過。

細節見 播放與外網連線。Mi302 回報自己是 Emby Server 4.8.11.0。

路徑和參數

進來的每個 HTTP 請求先整理路徑,再比對路由:

  • 路徑裡的英文字母轉小寫,其他字元不動(/Persons/Émilie 的 É 保留),所以路徑不分大小寫。
  • 開頭的 /emby、/mediabrowser 拿掉,有沒有這個前綴都可以。
  • 結尾的 / 拿掉。
  • 查詢參數的名稱不分大小寫(ParentId、parentid 都可以)。

其他:

  • 錯誤回應是純文字,和 Emby 一樣。
  • CORS 全開。
  • FastAPI 自動產生的 API 說明在 /api-docs。

認證

token 依序從這些地方找:

  1. 標頭 X-Emby-Token、X-MediaBrowser-Token。
  2. 標頭 X-Emby-Authorization(或 Authorization)裡的 Token="…",格式是 MediaBrowser Client="…", Device="…", DeviceId="…", Version="…", Token="…"。
  3. 查詢參數 api_key、X-Emby-Token、ApiKey、X-MediaBrowser-Token。

token 由 POST /Users/AuthenticateByName 發給。同一個使用者在同一個裝置(DeviceId)重新登入時,舊的 token 作廢。API 金鑰(設定檔的 api_keys,或在網頁上建立的)以管理員的身分執行(依名稱排序的第一個管理員),給 MoviePilot 這類程式用;它不能呼叫 /web/api/… 和 /p115/…。

路徑裡有使用者(/Users/{id}/…)或帶 UserId 參數時,讀寫的是那個人的觀看紀錄、續播點和收藏。一般使用者只能指定自己,指定別人回 403;管理員和 API 金鑰可以指定別人,找不到那個人回 404。id 不分大小寫,也接受有連字號的 GUID 寫法。沒指定就是登入的人。

已實作的 Emby 端點

路徑照 Emby 的寫法列出,實際上不分大小寫。「不用登入」以外的都要帶 token。

系統和使用者

端點 說明
GET /System/Info/Public 伺服器名稱、版本、id;不用登入
GET /System/Info 完整伺服器資訊
GET, POST /System/Ping 回 Emby Server;不用登入
GET /System/Endpoint 固定回「區域網路內」;不用登入
GET /Branding/Configuration 空的品牌設定;不用登入
GET /Users/Public 登入畫面的使用者清單(server.public_users 關掉時是空的);不用登入
POST /Users/AuthenticateByName 登入,JSON 或表單,密碼欄位是 Pw 或 Password;不用登入
GET /Users 所有使用者;要管理員
GET /Users/Query 同上,包成 {Items, TotalRecordCount}(MoviePilot 用它算有幾個使用者);要管理員
GET /Users/{id} 使用者資料;只能查自己,管理員可以查別人
POST /Sessions/Logout 登出,作廢這個 token
POST /Sessions/Capabilities、/Sessions/Capabilities/Full 接受但不處理
GET /Sessions 空清單
GET, POST /DisplayPreferences/{id} 固定的預設值,不儲存;不用登入
GET /Users/{id}/GroupingOptions、/Plugins、/Localization/{kind} 空清單;不用登入
GET /Items/{id}/RemoteImages 一律是空的(Mi302 不向外查圖,MoviePilot 會改用 /Items/{id}/Images/{類型});不用登入
WebSocket /embywebsocket 只回應 KeepAlive

媒體庫和項目

端點 說明
GET /Users/{id}/Views、/Library/MediaFolders 媒體庫清單
GET /Library/VirtualFolders、/Library/VirtualFolders/Query、/Library/SelectableMediaFolders 媒體庫和資料夾路徑(MoviePilot 用)
POST /Library/Refresh 全部重新掃描;要管理員
POST /Library/Media/Updated 只掃通知裡的路徑;要管理員
POST /Items/{id}/Refresh 只重掃這一項所在的劇或電影;要管理員
GET /Users/{id}/Items、/Items 查詢項目,見下面的參數
GET /Users/{id}/Items/{itemId}、/Items/{id} 單一項目的完整資料,含 MediaSources、People、Chapters;id 是人物 id 時回人物
GET /Users/{id}/Items/Latest 最近新增(Limit、ParentId、IncludeItemTypes;要集時以劇為單位)
GET /Users/{id}/Items/Resume 繼續觀看
GET /Items/{id}/Ancestors 上層項目
GET /Items/Counts 電影、劇集、集的數量
GET /Shows/{id}/Seasons 季
GET /Shows/{id}/Episodes 集(SeasonId、Season、StartIndex、Limit、Fields)
GET /Shows/NextUp 接著看(SeriesId、Limit)
GET /Genres 所有類型
GET /Persons、/Persons/{name} 搜尋人物、依名稱找人物(原名、中文名都認)
GET /Persons/{name}/Images/{type} 人物照片,302 到 nfo 裡的網址;不用登入
POST /Users/{id}/PlayedItems/{itemId};DELETE(或 POST …/Delete) 標記已看、未看;對劇或季就是底下所有集
POST /Users/{id}/FavoriteItems/{itemId};DELETE(或 POST …/Delete) 加入、移除收藏

/Items 的查詢參數:ParentId、Recursive、IncludeItemTypes(含 Person 時一併搜尋人物)、ExcludeItemTypes、Ids、PersonIds、Person、IsFolder、SearchTerm、NameStartsWith、Years、Genres、Filters(IsPlayed、IsUnplayed、IsFavorite、IsResumable)、IsPlayed、IsFavorite、SortBy、SortOrder、StartIndex、Limit、Fields(MediaSources、People、Chapters)、UserId。

StartIndex、Limit 是負數或看不懂時當成沒給。/Items 和 /Shows/{id}/Episodes 沒給 Limit 就是全部,和 Emby 一樣;最新、繼續觀看、接著看預設 20 項,人物搜尋預設 50 項,這幾個最多 500 項。Limit=0 只回總數。

SortBy 認得:SortName、Name、DateCreated、DateLastContentAdded、PremiereDate、ProductionYear、CommunityRating、CriticRating、Runtime、Random、DatePlayed、PlayCount、IndexNumber、ParentIndexNumber、AiredEpisodeOrder。

回空結果的端點(讓播放器不報錯):/Users/{id}/Items/{id}/Intros、/Items/{id}/Similar、/Movies/{id}/Similar、/Shows/{id}/Similar、/Videos/{id}/AdditionalParts、/Items/{id}/CriticReviews、/Users/{id}/Items/{id}/LocalTrailers、/Users/{id}/Items/{id}/SpecialFeatures、/Items/{id}/SpecialFeatures、/Items/{id}/ThemeMedia。

圖片

端點 說明
GET /Items/{id}/Images/{type}[/{index}] 讀圖片,type 是 Primary、Backdrop、Thumb、Logo、Art;不用登入
POST /Items/{id}/Images/{type}[/{index}] 上傳圖片(base64 文字或圖片檔本身,最大 30 MB),存在 data/images/;要管理員
DELETE /Items/{id}/Images/{type}[/{index}] 刪掉上傳的圖片,改回資料夾裡的圖;要管理員
GET /Users/{id}/Images/{type} 使用者頭像,一律 404

播放

端點 說明
GET, POST /Items/{id}/PlaybackInfo 媒體來源
GET, HEAD /Videos/{id}/{name} 串流(stream、stream.mkv、original.mkv…);redirect.require_auth 開著時要登入
GET, HEAD /Items/{id}/Download、/Items/{id}/File 下載;一律要登入,server.allow_download 關掉時回 403
POST /Sessions/Playing、/Sessions/Playing/Progress、/Sessions/Playing/Stopped 播放進度
POST /Sessions/Playing/Ping 接受但不處理
POST /Users/{id}/PlayingItems/{itemId}、…/Progress;DELETE(或 POST …/Delete) 舊版的播放進度回報

其他端點

端點 說明
GET /Episode/{id}/IntroTimestamps(也認 /v1)、GET /Episode/{id}/Timestamps Jellyfin Intro Skipper 外掛格式的片頭片尾
GET /MediaSegments/{id} Jellyfin 10.10 的媒體片段
GET, HEAD /d/{pickcode}[.副檔名][/檔名] Mi302 產生的 strm 用的短連結,302 到 115 直鏈;不用登入
GET, HEAD /p115/redirect?pickcode=…、/api/v1/plugin/p115strmhelper/redirect_url?pickcode=… 相容其他工具的 strm;不用登入
OPTIONS, PROPFIND, GET, HEAD /dav/{115 路徑} 只能讀的 WebDAV(webdav.enabled):PROPFIND 列資料夾(Depth 最多 1),GET 檔案 302 到 115 直鏈,HEAD 只回大小;要 HTTP Basic 登入,寫入的方法回 405

管理 API

管理網頁 /web 用的 API。115 的檔案、資料夾 id 有 19 位,超過 JavaScript 能精確表示的整數,回傳時一律是字串;送進來時字串、數字都接受。除了 /web/api/setup,都要用管理員帳號登入後的 token(放在 X-Emby-Token 標頭),API 金鑰不行。請求和回應都是 JSON。

路徑 用途
GET, POST /web/api/setup 是否需要建立第一個管理員;建立它(只在還沒有任何帳號時可用)
GET, PUT /web/api/settings 讀取、儲存網頁上的設定(寫進設定檔)
GET, POST /web/api/scan 掃描進度和各媒體庫的項目數;開始掃描:{"library": 名稱}、{"path": 路徑},都沒有就全部;沒在掃描時 last 是上一次掃描(範圍、處理了幾個、是否按了停止)
GET /web/api/libraries/userdata 這個媒體庫(name)有幾筆觀看紀錄、是幾個使用者的(刪除媒體庫的確認框用)
GET, POST /web/api/users;PUT, DELETE /web/api/users/{id} 使用者
GET /web/api/browse、/web/api/115/browse 選伺服器上的資料夾、選 115 目錄
GET /web/api/115/offline、POST /web/api/115/offline、POST /web/api/115/offline/delete、POST /web/api/115/offline/clear 115 雲下載:任務清單(page,附這個月還能加幾個);加任務(urls 一行一個、folder 存到的 115 路徑,空的用 115 預設);刪任務(hashes,files 連下載好的檔案一起刪);清除已完成或已失敗的紀錄(what:done/failed)
POST /web/api/115/offline/retry 重新加入失敗的雲下載任務(hash、url、folder_id):先刪那一筆失敗的紀錄(不刪檔案),再加回原來的資料夾
POST /web/api/115/share/read、POST /web/api/115/share/start、GET /web/api/115/share/status、POST /web/api/115/share/stop 轉存 115 分享(要用 cookie 登入):讀分享(text 一行一個連結;回傳每個分享的標題、大小、最上層的項目,讀不到的在 error 寫 115 的原文);在背景轉存(shares:[{code, receive_code, title, ids}],ids 是字串;folder 存到的 115 資料夾;target:path 整理到 target_path,空的先不整理);進度;停止(做完手上這一個分享)
POST /web/api/115/rapid/read、POST /web/api/115/rapid/start、GET /web/api/115/rapid/status、POST /web/api/115/rapid/stop 從阿里雲盤秒傳到 115(要用 cookie 登入 115、登入阿里雲盤):讀取(source 阿里雲盤路徑、media_only 只要影片和字幕;回傳幾支影片、字幕、總大小、沒有 SHA1 的、超過 115 單檔上限的、前 200 支);在背景秒傳(source、folder 存到的 115 資料夾、target:path 整理到 target_path,空的先不整理、media_only);進度和每一支的結果;停止(做完手上這一支)
GET /web/api/aliyun/status、POST /web/api/aliyun/token、POST /web/api/aliyun/logout、GET /web/api/aliyun/dirs 阿里雲盤:帳號狀態;登入(refresh_token,失敗時換回原本的);登出;選資料夾(path 這一層的子資料夾和檔案數,空的是「資源庫」「備份盤」)
GET /web/api/115/recyclebin、POST /web/api/115/recyclebin/clean 115 回收站的一頁(offset、limit);清空(永久刪除,要 {"confirm": "清空"},cookie 通道再帶 password 安全密鑰)
GET /web/api/deleted Mi302 送進 115 回收站的紀錄,新的在前面(source 只看一種來源、offset、limit);回傳 items、total、sources
GET /web/api/115/files 瀏覽 115:資料夾(cid、path,或只給 path)的子資料夾和檔案,影片附上在媒體庫裡的樣子(lib);檔案一次最多 1000 個,offset 要下一批
POST /web/api/115/delete 瀏覽 115 裡勾的刪掉(parent 現在看的資料夾 id、ids):先列一次那個資料夾,只刪真的在裡面的,移到 115 回收站;同步目錄裡的本機 strm 和媒體庫跟著拿掉
GET /web/api/115/organize、POST /web/api/115/organize/check、GET /web/api/115/organize/job 整理 115 網盤:要整理的資料夾(問過 MoviePilot 名稱不一樣的、集號不對的;q、kind 是 series/movie/episodes/held(先不整理的)、offset、limit;瀏覽 115 加進來的在 pinned;每一個的 held 是有沒有標先不整理),附背景檢查進度 job、還沒問過的 unchecked;在背景問 MoviePilot(refresh 連問過的也重問);整理工作的進度
POST /web/api/115/organize/folder、DELETE /web/api/115/organize/folder/{id} 瀏覽 115 裡挑的資料夾(cid、path)釘在清單最上面;拿掉
POST /web/api/115/organize/preview、POST /web/api/115/organize/recommend、POST /web/api/115/organize/execute 預覽一個資料夾(id;每一部分可以指定 parts: {部分: {type, tmdbid, season, format}};target 是 parent/auto/path 加 target_path),回傳預覽代碼 token;一部分的集數定位(id、part,先請 MoviePilot 推薦);照預覽代碼執行(tokens,cleanup 整理完把沒有影片留下的來源資料夾移到回收站)
POST /web/api/115/organize/all、POST /web/api/115/organize/all/stop、GET /web/api/115/organize/all 全部整理:清單上符合 q、kind 的(加上瀏覽 115 釘上來的)在背景一個一個預覽,沒問題(預覽的 review 是空的)就照預覽整理,其他跳過並記原因(target、target_path、cleanup;標了先不整理的、一次要送超過 max_videos 支影片的不做,預設 300,0 = 不限;MoviePilot 連不上時等它恢復,最多 15 分鐘);做完手上這一個就停;進度和跳過、失敗的明細(明細最多留 2000 個,results_total 是全部的數量,truncated 表示有沒有留不下的;進度也在 GET /web/api/115/organize/job 的 batch)
POST /web/api/115/organize/hold 先不整理(id、hold):全部整理時跳過、排到清單後面;回傳那一個
GET /web/api/115/organize/episodes、POST /web/api/115/organize/delete 刪除:一部劇的每一集和劇集資料夾(series,集號不對的標 problem);刪勾選的集(series_id、file_ids、remove_folder),或清單上的一個整個刪(id),都移到 115 回收站,本機 strm 和媒體庫跟著拿掉;{"ids": [...]} 勾選的在背景一個一個刪,進度看 organize/job 的 deleting
GET /web/api/server、POST /web/api/server/check、POST /web/api/server/update、POST /web/api/server/restart 版本和上次檢查更新的結果(boot 每次啟動都不同)、現在檢查、更新到最新版(背景執行,進度看 job)、重新啟動
GET /web/api/libraries/suggest?path= 批量新增媒體庫:列出子資料夾,附上建議的類型和理由、影片數(complete 表示數完了)、是否已在某個媒體庫裡(used)、要不要預設勾選;partial 表示有資料夾沒看完
POST /web/api/moviepilot/test、GET /web/api/moviepilot/status、POST /web/api/moviepilot/scrape MoviePilot 測試連線、狀態、刮削缺少資料的項目
GET /web/api/moviepilot/library-dirs MoviePilot 目錄設定裡存儲是 115 的媒體庫目錄(dirs 每一項有 name、path、type;enabled 是設定了 MoviePilot 沒有),給轉存分享的「整理到」;沒設定、讀不到時 dirs 是空的、error 寫原因
GET /web/api/series、POST /web/api/moviepilot/fill 劇集和集號空洞清單;補全缺集
GET /web/api/moviepilot/subscriptions、POST /web/api/moviepilot/subscriptions/clear、GET /web/api/moviepilot/subscriptions/unsubscribed MoviePilot 現在有幾個訂閱;取消所有訂閱({"confirm": "取消訂閱"});最近 5 次取消前的訂閱清單(backups)
GET /web/api/intro/status、POST /web/api/intro/clear 片頭片尾({"season_id": id} 只清一季學到的紀錄)
GET /web/api/intro/seasons?q=、PUT /web/api/intro/seasons/{季 id} 片頭片尾的季清單和搜尋;手動設定一季(all_seasons 套用到整部劇)
GET /web/api/people/status、POST /web/api/people/resolve 演職人員中文名
GET, POST /web/api/backups;GET /web/api/backups/{name} 備份清單、立即備份、下載備份
GET /web/api/mediainfo/status、GET /web/api/mediainfo/titles、POST /web/api/mediainfo/probe 媒體資訊:狀態;還缺媒體資訊的片名(可篩選、排序);提取(可帶篩選、limit、ids)
POST /web/api/mediainfo/probe/limit、POST /web/api/mediainfo/stop 提取中改這一批最多幾支({"limit": N});停止這一批
GET /web/api/logs、/web/api/logs/download 日誌、下載日誌檔
GET, POST /web/api/apikeys;DELETE /web/api/apikeys/{key} API 金鑰
GET /p115/status 115 帳號狀態
POST /p115/qrcode、GET /p115/qrcode/status、POST /p115/cookies、POST /p115/logout 掃碼登入、貼 cookie、登出
POST /p115/open/qrcode、GET /p115/open/qrcode/status、POST /p115/open/logout 115 開放平台授權
POST /p115/strm/sync?mode=incremental(不帶就是全量)、GET /p115/strm/status、PUT /p115/strm/tasks 同步、同步狀態、同步任務
POST /p115/strm/rewrite、POST /p115/strm/rewrite/stop 把現有的 strm 改成目前的伺服器網址(只動本機檔案);停止(改好的留著)
GET /web/api/dupes、POST /web/api/dupes/scan、GET /web/api/dupes/groups、GET /web/api/dupes/shows、POST /web/api/dupes/delete、GET /web/api/dupes/log 115 重複檔案:狀態、找重複(paths)、分組清單、刪除(overrides、sha1+size 只刪一組、dry_run 只試算);kind=versions 是不同版本(grp 只刪一組、show 只刪一部、use_suggestions 照建議;網頁的清單用 GET /web/api/dupes/shows?kind=versions,同一部劇合成一列,展開時 groups?kind=versions&show=…;完全相同一樣用 shows?kind=exact、groups?show=…,刪除帶 show 只刪那一部)、刪除紀錄;kind=big 是大檔案(min_size 位元組、type 是 movie/episode/none 篩選,刪除時 use_suggestions 是符合條件的全部,不必留一份);POST /web/api/dupes/prefer 換不同版本建議保留的解析度(prefer 是 1080、2160 或 highest)
POST /web/api/scan/stop、POST /p115/strm/stop、POST /web/api/dupes/stop、POST /web/api/moviepilot/stop、POST /web/api/115/organize/check/stop、POST /web/api/115/organize/execute/stop、POST /web/api/115/organize/delete/stop 停止掃描、115 同步、找重複/刪重複、MoviePilot 刮削、補全缺集或取消訂閱(what 是 scrape、fill 或 unsubscribe)、整理 115 網盤的檢查、執行、刪除勾選的:都在安全的地方停下,沒做完的下次再做;回傳 stopped(沒在跑是 false)

GET /web 是管理網頁本身,/web/115 轉到 /web#115。

程式結構

embyserver/ 底下每個模組:

模組 內容
__main__.py 命令列入口:python -m embyserver [-c config.yaml] [--scan] [--sync-115 [incremental]] [--reset-password 帳號 新密碼]
app.py 組裝 FastAPI 程式、整理路徑、啟動背景工作
auth.py 使用者、token、Emby 認證標頭
backup.py 每天備份資料庫和設定檔
browse115.py 瀏覽 115:列資料夾,影片對照媒體庫
config.py 設定檔的資料結構和讀取
config_file.py 把設定寫回 config.yaml
settings.py 網頁上可改的設定:驗證、套用、和設定檔同步
db.py SQLite 資料表和存取
dupes.py 115 上的重複檔案:完全相同、不同版本、建議保留哪一份;大檔案
dto.py 資料庫列轉成 Emby 的 BaseItemDto、UserDto、MediaSourceInfo
filetypes.py 共用的副檔名清單
organize115.py 整理 115 網盤:問 MoviePilot 每個資料夾整理後叫什麼(結果記在 organize_checks),整個資料夾交給它預覽
http_util.py 呼叫外部服務的 httpx Client,把連線錯誤轉成看得懂的原因
intro.py 從播放行為學片頭片尾
library_suggest.py 批量新增媒體庫:猜子資料夾是電影還是劇集
logs.py 日誌:終端、日誌檔、記憶體,遮掉憑證
mediainfo.py 媒體資訊:ffprobe 結果和 X-mediainfo.json 轉成 Emby 格式
prober.py 用 ffprobe 探測 strm 指向的影片
moviepilot.py 刮削、補全缺集交給 MoviePilot
moviepilot-plugin/ MoviePilot V3 外掛「Mi302 整理助手」(package.v3.json、plugins.v3/mi302organizer/):照 MoviePilot 的規則算名字(naming.py),用 MoviePilot 的 115 授權照 Mi302 送來的清單批次改名,見 MoviePilot 整合
p115.py 115:掃碼登入、取直鏈、列目錄、生活事件、熔斷
p115_open.py 115 開放平台通道
probe_select.py 媒體資訊:挑要提取的片名(篩選、排序)
people.py 演職人員、中文名、類型中文化
redirect.py 解析 strm 真實網址、路徑替換、重導向快取
reorganize.py 整理的執行(照預覽代碼送 MoviePilot、清掉搬空的舊資料夾、之後的增量同步)和刪除;集數定位模板(MoviePilot 推薦不出來時的備用)
scanner.py 媒體庫掃描
share115.py 轉存 115 分享:讀分享、轉存到暫存子資料夾、等 115 做完,加進「整理 115 網盤」交給 MoviePilot 整理
rapid115.py 從阿里雲盤秒傳到 115:115 的上傳初始化(加密、二次驗證),整批秒傳,完成的資料夾加進「整理 115 網盤」
aliyun.py 阿里雲盤開放平台:登入和換 token、列目錄、取下載網址、讀一段
strm_sync.py 從 115 產生 strm、下載中繼資料
textutil.py 拼音排序、拼音搜尋、繁簡轉換
updater.py 網頁上的檢查更新、更新、重新啟動
routes/system.py System、Users、Sessions 等端點
routes/items.py 媒體庫、項目查詢、使用者資料、圖片
routes/playback.py PlaybackInfo、串流、進度回報、片頭片尾端點
routes/p115.py 115 登入和同步的管理 API、pickcode 短連結
routes/web/ 管理網頁和它的 API,照分頁分成 setup、scan、intro、moviepilot、organize、p115、server 幾個檔案
routes/common.py 路由共用的小工具
web/admin.html 管理網頁(單一檔案,HTML、CSS、JavaScript 都在裡面)

資料庫 library.db 的資料表:meta(伺服器 id、115 登入狀態、同步進度等)、users、tokens、items、user_data(觀看紀錄、收藏)、p115_index(115 檔案 id 對本機路徑)、media_info、people、person_names、intro_obs、mp_no_image。

開發和測試

需要 Python 3.10 以上。在 venv 裡:

pip install -r requirements.txt pytest ruff
python -m pytest
ruff check embyserver tests

測試也會用 node --check 檢查管理網頁的 JavaScript 語法(沒有 node 就略過),並檢查說明文件:Wiki 的連結和錨點都找得到、每一頁都在側欄、三種語言的設定檔參考都寫到每一個鍵。

改了 install.sh 之後:

shellcheck install.sh

參考過的專案

  • DDSRem-Dev/MoviePilot-Plugins:302 播放流程參考 embyreverseproxy 外掛,差別是 Mi302 背後不需要真的 Emby,Emby API 自己實作;115 掃碼登入和取直鏈的流程參考 p115strmhelper。
  • StrmAssistant(Emby 神醫助手):X-mediainfo.json 沿用它「媒體資訊持久化」的格式;片頭片尾的學法參考它的「片頭探測 - 播放行為」。
  • xiao-vvv/emby-mediainfo(MIT 授權):ffprobe 到 Emby 欄位的對照、探測的限速和 115 熔斷規則改寫自這個專案,版權聲明保留在 embyserver/mediainfo.py。
⚠️ **GitHub.com Fallback** ⚠️