12 KiB
12 KiB
title, tags, status
| title | tags | status | ||
|---|---|---|---|---|
| tver の現状 |
|
DB の土台を実装中 |
tver の現状
[!info] このファイルの位置づけ tver プロジェクトの現状(決定事項・要件・構成・課題)を積み上げていく。運用規約は AGENTS を参照。
今の状態
- Go の HTTP サーバー、TVer API クライアント、ドラマ・バラエティの新着表示、テスト、web コンテナの定義がある
- DB の土台を実装中。 SQL マイグレーションと待ち行列・番組登録の操作を追加した。web・worker への接続と画面は今後実装する
- リポジトリ:
https://gitea.kazu634.com/kazu634/tver.git
目的
TVer(https://tver.jp/)で公開中の動画情報を取得し、選んだ動画を yt-dlp でダウンロードし、アプリ内で再生する Web アプリを作る。
決定事項
| 項目 | 決定 |
|---|---|
| TVer 利用規約との関係 | 鑑賞を前提とするダウンロードのため、問題無しと判断(ユーザー判断) |
| 利用者・公開範囲 | 自分のみ・LAN 内だけ。EC2 nginx では公開しない |
| 保存先 | NAS(ds923p) |
| 自動ダウンロード | 作る(番組を登録し、新しい回を自動でダウンロードする) |
| 再生 | アプリの中で再生する |
| ダウンロード | yt-dlp を使う |
| デプロイ | Dockge で管理する複数コンテナ構成。dockage VM の /opt/stacks/tver |
| 開発言語 | Go(web・worker とも)。yt-dlp は外部コマンドとして呼び、進捗・結果は yt-dlp の出力から読み取る |
| DB | NAS 上の tver 専用 Postgres 17 コンテナ(Gitea と同じ方式)。バックアップは規約どおり pg_dump → NAS。公開ポートは未定(候補 15434、実機で確認する) |
| DB ドライバ | github.com/jackc/pgx/v5 の pgxpool |
| DB テスト | github.com/fergusstrange/embedded-postgres v1.34.0 の Postgres 17 を起動する。Apple Silicon では Rosetta 2 が必要 |
| 定期実行 | worker 内のスケジューラ |
| NAS のマウント | NFS。DSM で 192.168.10.155 のみ許可する |
| 保持期間 | 削除は手動のみ。自動削除はしない |
| 待ち行列 | Postgres のテーブルだけで実装する。専用のキュー基盤は持たない |
| 定期確認の間隔 | 登録番組の新しい回を 24 時間ごとに確認する |
要件
機能要件
| # | 要件 | 補足 |
|---|---|---|
| F1 | ドラマ・バラエティの新着一覧を表示する | 番組名・回タイトル・放送局・長さ・配信終了日時・サムネイル |
| F2 | 一覧を絞り込む | 予告(「60秒PR」等)を除外できる。ダウンロード済み・待ち行列中を区別して表示する |
| F3 | 選んだ動画をダウンロードの待ち行列に入れる | 複数選択できる |
| F4 | ダウンロードの進捗・結果を表示する | 失敗理由(DRM・地域制限・TVer 側の変更による抽出失敗)を区別して表示する |
| F5 | 同じ回を二重にダウンロードしない | 手動・自動の両方で共通 |
| F6 | 番組(シリーズ)を登録し、新しい回を自動でダウンロードする | 登録・解除・一覧。定期的に新しい回を確認する |
| F7 | ダウンロード済みの動画をアプリ内で再生する | シーク(HTTP Range)に対応する |
| F8 | ダウンロード済みの動画を一覧・削除する | NAS の容量管理のため |
| F9 | 保存ファイルの名前と置き場所を規則で決める | 例: <番組名>/<番組名> <回タイトル> [<エピソードID>].mp4 |
非機能要件
- yt-dlp を素早く更新できること。 TVer 側の変更で抽出が壊れやすいので、worker のイメージを再ビルドするだけで更新できるようにする
- TVer への負荷を抑える。 一覧取得はキャッシュし、同時ダウンロード数は 1〜2 に制限する
- ブラウザで再生できる形式で保存する。 映像 H.264 + 音声を MP4 に結合する(ffmpeg が必要)
- 認証は持たない(LAN 内限定のため)
- homelab の規約に従う(homelab/docs/conventions): Compose v2、秘匿情報は
.env、TZ=Asia/Tokyo
構成(案)
| コンテナ | 役割 |
|---|---|
web |
UI・API。新着一覧の取得、待ち行列への登録、番組登録、動画の配信(再生) |
worker |
Go + yt-dlp + ffmpeg。待ち行列からダウンロードし、登録番組の新しい回を定期的に確認する |
- 現在の
webは/healthz、ドラマ・バラエティの新着ページ(/)、新着一覧の HTML 断片(/newer)を提供する。「待ち行列/登録番組/ライブラリ」のページは空のまま - 新着はジャンルごとにメモリへ 10 分間キャッシュする。キャッシュ中は TVer セッションの再発行と一覧の再取得を行わない。TVer の取得に失敗した場合、一覧の場所にエラーを表示して詳細をログへ記録する
- 「予告を隠す」は既定でオンとし、180 秒以下の件を非表示にする。長さは秒から分へ切り上げて表示し、配信終了日時は Asia/Tokyo で表示する。ジャンルと予告表示の状態は URL に残す
- 画面は
html/templateと htmx 2.0.11 を使い、htmx は Go のembedから/static/でローカル配信する compose.yamlは web のみを定義し、dockage VM の LAN アドレス192.168.10.155:8080で公開する。待ち受けはWEB_ADDRで変更でき、既定は:8080- DB は NAS 上に tver 専用 Postgres 17 コンテナを置く。
internal/storeの埋め込み SQL をschema_migrationsで管理し、pg_advisory_lockで複数プロセスからの同時適用を直列化する。起動処理への組み込みは今後行う download_jobsはepisode_idを一意にして二重登録を防ぎ、ジョブの状態・段階別進捗・結果を持つ。subscriptionsはシリーズ単位の登録と最終確認日時を持つ。worker からの操作と画面用の一覧取得をinternal/storeにまとめる- worker は
yt-dlp[default]と ffmpeg を外部コマンドとして使い、映像・音声・結合・移動の段階別進捗を扱う。正確な全体パーセントは保存しない。after_move:filepathで実パスを取り、ffprobe で H.264 と AAC の MP4 を確認して成功とする。失敗は DRM、地域制限、ネットワーク、配信不可、抽出失敗、出力不正、その他の順に分類する - 保存先の NAS 共有フォルダは dockage VM に NFS でマウントし、
worker(書き込み)とweb(読み取り・削除)にバインドマウントする
外部とのつながり
| 相手 | 用途 | 備考 |
|---|---|---|
TVer platform-api.tver.jp |
新着一覧(/service/api/v1/callNewerDetail/{drama|variety})・番組のエピソード一覧 |
非公開 API。セッションは /v2/api/platform_users/browser/create で発行する(yt-dlp と同じ方式) |
| yt-dlp | ダウンロード | TVer extractor を使う |
| NAS ds923p(NFS) | 動画の保存先 | DSM で NFS を有効化し、共有フォルダを 192.168.10.155 のみに許可する |
| NAS ds923p(Postgres 17) | DB | tver 専用コンテナを使う。DSM 内蔵の Postgres が 5432 を使うため別ポートで公開し、DSM ファイアウォールで 192.168.10.155 のみ許可する。公開ポートは実機で決める |
確認済みの事実
- 新着 API は
drama/varietyそれぞれ 100 件を返す。予告動画も含まれる。各件にid・seriesID・seriesTitle・title・broadcasterName・duration・endAt・thumbnailPathがある [実機] 2026-10-03 確認 - dockage VM から TVer API に到達でき(HTTP 200)、送信元は日本と判定される [実機] 2026-10-03 確認
- yt-dlp 2026.08.19 で TVer のエピソードを抽出できる(
--simulate)。映像(H.264 /avc1)と音声が別ストリームで、結合に ffmpeg が要る。1080p・約 50 分で約 1.2GiB [実機] 2026-10-03 確認 - 5 分の
epq432nee4を yt-dlp 2026.08.19 で取得した。--newlineと--progress-templateの 1 行 1 JSON は Go の外部プロセスから逐次読める。ただしuvx yt-dlpのみでpycryptodomexが無いと AES-128 HLS を ffmpeg に委譲し、途中のダウンロード進捗が出なかった。yt-dlp[default]に含まれるpycryptodomexを加えた試行では途中の進捗が出た [実機] 2026-10-03 確認 - 別ストリームの映像と音声は進捗が個別に始まり、
format_idで区別できる。結合処理はpostprocess:の出力で検知でき、--print after_move:filepathで実ファイルのパスを取得した。完成した MP4 は 24,613,787 バイトで、ffprobe では H.264 640×360 と AAC LC だった。初回取得は約 10 秒だった [実機] 2026-10-03 確認 - SIGTERM で止めると
.partと.ytdlが残り、再実行で 437,066 バイトから 438,090 バイトへ続きから取得できた [実機] 2026-10-03 確認 - 到達不能のプロキシ経由の通信失敗と存在しないエピソードは、ともに yt-dlp の終了コード 1 だった。stderr の文言で分類が必要。DRM・地域制限の失敗文言と終了コードは実測していない(yt-dlp の extractor ソースのみ確認) [実機] 2026-10-03 確認
- yt-dlp の TVer extractor は日本のみ(
_GEO_COUNTRIES = ['JP'])。DRM 付きの作品は取得できない [公式] https://github.com/yt-dlp/yt-dlp/blob/master/yt_dlp/extractor/tver.py (取得日: 2026-10-03) - dockage VM に NAS のマウントは無い。ルートの空きは約 20GB [実機] 2026-10-03 確認
課題
- 新着 API が 100 件で打ち切りか、続きを取得できるか [要確認]
- 新着のうち DRM 付きの作品の割合 [要確認]
- 非公開 API と yt-dlp の TVer 対応は、TVer 側の変更で予告なく壊れうる。壊れたことを検知する手段が要る
- NAS の準備(NFS 共有フォルダ、tver 専用 Postgres 17 コンテナ、DSM ファイアウォール)は DSM での手作業になる。DB の公開ポート(候補 15434)は本番前に実機で確認する [要確認]
- DRM・地域制限の yt-dlp 失敗時の文言と終了コードは未実測 [要確認]
確かめ方
- Codex では
go test -count=1 ./...を環境変数の前置なしで実行する。go testを許可する rules に一致させ、Postgres の共有メモリ作成に必要な権限で実行するため。初回のモジュール取得にはネットワーク接続が必要 - DB テストは
embedded-postgresv1.34.0 が Postgres 17 を一度起動し、テストごとに独立した DB を作る。バイナリキャッシュ、実行ファイル、データは/private/tmp配下に置く。Apple Silicon での実行には Rosetta 2 の導入が必要 - TVer 応答のテストは
internal/tver/testdata/newer_drama.json(2026-10-03 の実応答を 3 件に縮小)とhttptestを使い、テストから本物の TVer にはアクセスしない - 新着画面のテストは偽物の TVer クライアントと差し替え可能な時計を使い、表示項目、絞り込みの境界、10 分キャッシュ、取得失敗、JST 表示を確認する
env GOPATH=/private/tmp/tver-gopath GOCACHE=/private/tmp/tver-go-cache GOMODCACHE=/private/tmp/tver-go-modcache GOPROXY=off go vet ./...で静的解析するWEB_ADDR=127.0.0.1:18080 go run ./cmd/webで起動し、/healthz、/、/newer?genre=varietyを確認できる。TVer への実アクセスを伴うため手動確認は少数回に留める