- yarn や poetry はそれぞれ
client/とserver/のディレクトリに移動した状態で実行してください。ルートディレクトリにはパッケージ管理系のファイルは一切配置していません。 - サーバー側では poetry を使っているので、python コマンドは必ず全て poetry run 経由で実行します。python を直接実行すると .venv/ 以下のライブラリがインストールされていないために失敗します。
- 依頼を受けた時点で、サーバー API は次のいずれかの状態で常駐しています。いずれもユーザーが管理しているプロセスであり、エージェントが直接起動・停止すべきではありません
- リロードモード:
server/でpoetry run task devで起動。コード変更が hot reload されます。基本的にこの状態で依頼が来ます - リロードなしの開発サーバー:
server/でpoetry run task serveで起動。hot reload なしのユーザー権限プロセスです - pm2 常駐:
sudo pm2 start KonomiTVで起動。KonomiTV は root 側の pm2 プロファイルにしかインストールされていないため、pm2コマンドの実行には必ずsudoが必要であり、エージェントがユーザーの許可なくpm2を実行することはできません
- リロードモード:
- FastAPI の listen ポートは常に 7000 で固定です (Akebi HTTPS Server が
127.0.0.77:7010をリバースプロキシしています) - サーバー側コードを変更して挙動を確認したい場合の手順:
- リロードモードで動いている場合は、変更が自動で反映されます
- リロードなし開発サーバー / pm2 常駐で動いている場合は、ユーザーに「リロードモードでの起動への切り替え、もしくはサーバー再起動」を依頼してください
- エージェントが直接
python KonomiTV.pyやpoetry run python KonomiTV.pyを実行するのは禁止です。サーバーの起動には必ず taskipy で定義済みのpoetry run task serve/poetry run task devを使用してください (それでも、上記の通り既存プロセスとの衝突を避けるためエージェント自身が起動することは原則避けてください)
- クライアントの開発サーバーは普段は起動していません
- UI を検証する必要がある場合は、
client/でyarn devをエージェントが起動して構いません- 起動すると port 7001 で Akebi HTTPS Server 経由でリッスンされます (内部の Vite は
127.0.0.77:7011でリッスンします)
- 起動すると port 7001 で Akebi HTTPS Server 経由でリッスンされます (内部の Vite は
- 重複起動は禁止です。起動前に必ず
ps -ef | grep viteなどで既存プロセスの有無を確認してください yarn devで起動するクライアントは、開発モード時のみ同じドメインの:7000のサーバー API を直接叩くようハードコードされています (client/src/utils/Utils.ts のUtils.api_base_urlを参照)。Vite の proxy 設定は不要です- Chrome DevTools MCP からの検証時は
https://my.local.konomi.tv:7001にアクセスしてください - クライアント開発サーバー経由で API リクエストが想定通りに動かない場合でも、サーバーを立て直そうとしないでください。まず
Utils.api_base_urlの DEV 分岐の挙動を読み直し、port 7000 で動いているサーバー側の状態をps -ef | grep KonomiTVなどで確認してください - 番組タイトルや番組概要を表示するときは必ず
ProgramUtils.decorateProgramInfo(program, 'field_name')を使用してください
/Develop/KonomiTV-Docker以下には別途 Docker 版のステージング環境があります (port 7100、内部 HTTP は127.0.0.77:7110)- 本リポジトリの開発環境とは独立した別プロセスです
- KonomiTV はクリップボードなど Secure Context (HTTPS) でしか動作しない API を使用しています
- localhost 以外でも正規の HTTPS で提供できるよう、Akebi HTTPS Server が
akebi.konomi.tvの keyless server を経由してリバースプロキシを行っています - HTTP に直接アクセスされると Secure Context API が動かず混乱を招くため、内部の HTTP は
127.0.0.77でリッスンする構成になっています
KonomiTV は、クライアント・サーバーアーキテクチャに基づく Web アプリケーション (PWA) です。 以下の2つの主要部分で構成されています。
KonomiTV が一般的な Web サービスと異なる点は、フロントエンドと API サーバーの両方が各ユーザーの PC 環境で動作する点です。 したがって、Windows と Linux の両方で動作するように開発する必要があります。 Windows では Windows サービス、Linux では pm2 サービスとして動作するよう設計しています。
client/: KonomiTV のフロントエンドアプリケーション (PWA)- TypeScript
- yarn v1
- Vite
- Vue.js 3.x
- Vuetify 3.x
- Pinia
server/: KonomiTV のバックエンド API サーバー- Python 3.11
- Poetry
- Uvicorn
- FastAPI
- Pydantic v2
- Tortoise ORM
- SQLite (ローカル動作が必要なため MySQL や PostgreSQL は採用できなかった)
- Aerich
public/: 直接提供される静的ファイルsrc/: ソースコードviews/: Vue ルートコンポーネント/ページTV/: テレビ視聴関連ページVideos/: 動画関連ページReservations/: 予約関連ページSettings/: アプリケーション設定ページLogin.vue: ログインページRegister.vue: アカウント登録ページMyList.vue: マイリストページWatchedHistory.vue: 視聴履歴ページMyPage.vue: マイページNotFound.vue: 404 エラーページ
components/: Vue コンポーネントWatch/: テレビ・録画番組視聴画面向けコンポーネント群Panel/: 視聴画面右側のパネル内表示用コンポーネント群Twitter/: ツイート検索/タイムライン表示/キャプチャ管理/ツイート表示用コンポーネント群
Settings/: 設定ページから呼び出されるダイアログコンポーネント群HeaderBar.vue: ヘッダーバーSPHeaderBar.vue: スマートフォン用ヘッダーバーNavigation.vue: ナビゲーションBottomNavigation.vue: スマートフォン用下部ナビゲーションSnackbars.vue: 通知メッセージ表示コンポーネントBreadcrumbs.vue: パンくずリスト表示コンポーネント
stores/: 状態管理 (Pinia ストア)services/: サーバー API へのサービスクライアントplayer/: KonomiTV の視聴画面で用いられるライブ/ビデオプレイヤーのロジック (重要)managers/: PlayerController に紐づく様々な機能のロジックを提供し、各機能に責任を持つ PlayerManager 群PlayerController.ts: 動画プレイヤーである DPlayer に関連するロジックを丸ごとラップするクラスで、KonomiTV の再生系ロジックの中核を担う
utils/: ユーティリティ関数とヘルパーworkers/: 重い処理をバックグラウンドで実行するための Web Workers コード (with Comlink)styles/: グローバル CSS の定義 (グローバル CSS はApp.vueの方がメイン)router/: Vue Router 設定plugins/: Vue プラグインの初期化定義App.vue: アプリケーションのルートコンポーネント (グローバル CSS 定義もここに含まれる)main.ts: アプリケーションのエントリーポイント・初期化処理
package.json: Node.js プロジェクト設定と依存関係 (yarn)vite.config.mts: Vite ビルド設定tsconfig.json: TypeScript 設定.eslintrc.json: ESLint コードスタイル設定
app/: FastAPI アプリケーションコードrouters/: API ルートハンドラーChannelsRouter.py: チャンネル関連メタデータ取得 APIProgramsRouter.py: 番組関連メタデータ取得 APIVideosRouter.py: 録画番組メタデータ取得 APISeriesRouter.py: 番組シリーズ関連 APILiveStreamsRouter.py: 放送中テレビ放送のライブストリーミング配信関連 APIVideoStreamsRouter.py: 録画番組のストリーミング配信関連 APIReservationsRouter.py: EDCB と連携したテレビ番組の録画予約関連 APIReservationConditionsRouter.py: EDCB と連携したテレビ番組の自動録画予約条件 (EPG 自動予約) 関連 APIDataBroadcastingRouter.py: データ放送のインターネット接続機能向け APICapturesRouter.py: キャプチャ画像管理 APITwitterRouter.py: Twitter 連携 APINiconicoRouter.py: ニコニコ実況連携 APIUsersRouter.py: ユーザーアカウント管理 APISettingsRouter.py: クライアント・サーバー設定管理 APIMaintenanceRouter.py: サーバーメンテナンス用 APIVersionRouter.py: バージョン情報 API
models/: データベースモデルとスキーマChannel.py: チャンネル情報を管理するモデル(放送局情報、チャンネル番号、ロゴ、ストリーム設定など)Program.py: 放送番組情報を管理するモデル(番組メタデータ、EPG 番組情報、タイトル、番組詳細、ジャンルなど)RecordedProgram.py: 録画済み番組のメタデータを管理するモデル(EPG 録画番組情報、録画開始/終了時刻など)RecordedVideo.py: 録画済み番組の動画ファイル情報を管理するモデル(ファイルパス、映像/音声コーデック、ファイルサイズなど)Series.py: 番組シリーズ情報を管理するモデル(シリーズ名、シリーズ ID など)SeriesBroadcastPeriod.py: 番組シリーズの放送期間情報を管理するモデルTwitterAccount.py: Twitter アカウント連携情報を管理するモデル(トークン、認証情報など)User.py: ユーザーアカウント情報を管理するモデル(認証情報、権限など)
migrations/: Tortoise ORM のマイグレーションツール: Aerich 向けの DB マイグレーション定義 (Aerich で自動生成されたコードを修正したもの)streams/: テレビ放送のライブストリーミング・録画番組のオンデマンドストリーミング関連の実装LiveEncodingTask.py: ライブストリーミング用のエンコード・ストリーミングタスクを管理VideoEncodingTask.py: 録画番組用のエンコード・ストリーミングタスクを管理LiveStream.py: 放送波のライブストリーミングの状態管理VideoStream.py: 録画番組のオンデマンドストリーミングの状態管理LivePSIDataArchiver.py: 放送波から PSI/SI データを抽出・アーカイブする機能の実装
metadata/: 録画番組データから番組情報などのメタデータを抽出・保存するための実装RecordedScanTask.py: 録画フォルダの監視とメタデータの DB への同期を行うタスクMetadataAnalyzer.py: 録画ファイルのメタデータを解析するクラスTSInfoAnalyzer.py: 録画 TS ファイルや録画データ関連ファイルに含まれる番組情報を解析するクラスThumbnailGenerator.py: プレイヤーのシークバー用タイル画像と、候補区間内で最も良い1枚の代表サムネイルを生成するクラスCMSectionsDetector.py: 録画 TS ファイルに含まれる CM 区間を検出するクラス
utils/: ユーティリティ関数とヘルパーedcb/: EDCB 連携用の API クライアント実装JikkyoClient.py: ニコニコ実況・NX-Jikkyo 連携用の API クライアント実装TwitterGraphQLAPI.py: Twitter API 連携用にリバースエンジニアリングして開発した API クライアント実装TSInformation.py: 日本のテレビ放送で用いられている MPEG2-TS から情報を取得する際に役立つユーティリティ群OAuthCallbackResponse.py: OAuth 認証のコールバック時にブラウザに情報を渡すために返す特殊なレスポンスDriveIOLimiter.py: ドライブごとの同時実行数を制限するためのユーティリティクラスProcessLimiter.py: プロセスごとの同時実行数を制限するためのユーティリティクラス
app.py: FastAPI アプリケーションやルーターの初期化・バックグラウンドタスクの定義config.py: サーバー設定 (config.yaml) のロードとバリデーションconstants.py: サーバー全体で用いられるグローバル定数logging.py: ロギング設定schemas.py: API リクエスト/レスポンス型に用いる Pydantic スキーマ
data/: アプリケーションデータ用ディレクトリdatabase.sqlite: SQLite データベースファイル
logs/: アプリケーションログ用ディレクトリmisc/: メンテナンス・デバッグ用 Pythonスクリプト群static/: サーバー API によって提供される静的ファイル (Git 管理下にあり、放送局ロゴなどが含まれる)thirdparty/: FFmpeg や QSVEncC などのエンコーダーをはじめとした、ビルド済みのサードパーティー実行ファイル (Git 管理外で、poetry run task update-thirdpartyで更新する)pyproject.toml: Python プロジェクト設定と依存関係 (Poetry)KonomiTV.py: KonomiTV サーバーのエントリーポイントKonomiTV-Service.py: Windows サービス管理スクリプト & Windows サービスのエントリーポイント
LiveStream.connect()とLiveEncodingTask.run()は相互依存の関係にある。チャンネル切り替え時はconnect()が旧タスクをcancel()し、CancelledErrorがController()内で捕捉されてクリーンアップに到達するEDCBTuner._isOwner()チェックは二重操作を防ぐガードレールで、handoff()で所有権を移譲した後は旧ストリームからのclose()/disconnect()はこのチェックで弾かれる- Python 3.11 では
CancelledErrorを捕捉するとキャンセルカウンターがデクリメントされ、以降のawaitは正常に動作する。asyncio.wait()はタスク状態を変更しないが、asyncio.wait_for()はタイムアウト時にタスクを再度 cancel するので挙動が異なる点に注意する - より詳細な処理の流れは
server/app/streams/LiveEncodingTask.py内のコメントを参照すること
- 録画再生のシークは
server/app/streams/VideoStream.pyとserver/app/streams/VideoEncodingTask.pyが担当する。関連する実行時ログはライブ視聴と共通のserver/logs/KonomiTV-Server.logに出力され、エンコーダー専用ログは存在しない VideoStream.resolveSegmentSourcePosition()はsegment_mapキャッシュ・TS シーク・MP4 キーフレームテーブルのどの経路でソース位置を解決したかと所要時間を必ずログに出す。この経路の切り分けが録画再生のシーク不具合調査の起点になるため、ログ出力を削らないことVideoEncodingTaskは入力側 TS のキーフレームをTSKeyFrameCollector(server/app/utils/TSKeyFrameSeeker.py) で収集する。エンコーダー出力側のキーフレームはこのキャッシュにおける有効な入力開始位置にはならないsegment_mapキャッシュは録画 ID ごとにバッチ化・直列化されており、ロックはWeakValueDictionary[int, asyncio.Lock]で保持する。これにより、再生中でない録画のロックオブジェクトは自然に解放される- TS キーフレームキャッシュの経路と
TSKeyFrameSeeker.seek()は同じ DTS 座標系 (source_base_dts + playlist_start_seconds) を使うため、両者は直接比較できる
RecordedScanTask.run()は起動時の全件スキャン (runBatchScan()) と新規ファイルの監視 (watchRecordedFolders()) をasyncio.gather()で同時に実行しており、起動時スキャンが完了する前から新規録画の監視は動いているrunBatchScan()はfolder.rglob('*')で見つかった順にファイルを処理する。processRecordedFile()はfile_created_at/file_modified_at/file_sizeが DB の既存レコードと一致すれば重い解析をスキップするため、再スキャンでも変化のないファイルは軽く処理される- 過去に「起動時スキャンで全ファイルを先に収集し
st_ctimeでソートしてから新しい録画を優先処理する」という改善提案があったが、この方式は却下した。全件収集してからソートする実装は、最初の1件を処理し終えるまでに環境依存の固定 IO コストを必ず払うことになり、HDD・NAS・SMB など幅広いストレージ構成でこのコストが無視できない大きさになる。現状の「見つかった順に処理しつつ監視も並行させる」設計の方が、新規録画の反映を遅らせるリスクがなく安全
- サービス名はコード・型・コメント・UI・文書のすべてで一貫して
Twitterと表記する。XはX Premiumなどの正式な商品名、x.comなどのホスト名、HTTP ヘッダー名のように原表記が技術的に必要な場合だけ使用する - コードをざっくり斜め読みした際の可読性を高めるため、日本語のコメントを多めに記述する
- コードを変更する際、既存のコメントは、変更によりコメント内容がコードの記述と合わなくなった場合を除き、コメント量に関わらずそのまま保持する
- ログメッセージに関しては文字化けを避けるため、必ず英語で記述する
- それ以外のコーディングスタイルは、原則変更箇所周辺のコードスタイルに合わせる
- 不要な薄いラッパーや別名関数は作らず、責務のあるコンポーネントだけを追加する。
- コメントは冗長なくらいでちょうどよい。条件分岐・ループ・例外処理の直前にはその意図を書き、Python では
__init__()で代入するインスタンス変数には「保持する情報」「参照されるメソッド」「前提条件」を必ずコメントとして記す。クラス Docstring には責務のみを記載し、引数説明は__init__()の Docstring に集約する - Enum・Literal・Union 型の文字列表現は
tweet_capture_watermark_position: 'None' | 'TopLeft' | 'TopRight' | 'BottomLeft' | 'BottomRight';のように基本的に UpperCamelCase で命名する必要がある - 通常の Web サービスではないかなり特殊なソフトウェアなので、コンテキストとして分からないことがあれば別途 Readme.md を読むか、私に質問すること
- DB レコードの Pydantic / TypeScript 定義では、親となるレコード本体のスキーマを最上位に配置し、その下に子スキーマをフィールドの定義順に従って並べる
- JSON フィールドの値を生成する際は、辞書リテラル (
{}) を直接書くのではなく、TypedDict のコンストラクタを使用して型構造を明示的に示す - 画像の幅・高さ・総数・間隔など、視覚的に重要な情報を持つフィールドは定義の上部に集約し、重要度の高い順に配置することで一目で把握できるようにする
- 親スキーマから子スキーマへの並び順を徹底し、関連する子スキーマは親となる DB レコードスキーマの直下にまとめて配置する。可読性を損なうような配置変更は行わない
- TypeScript 側のスキーマ定義も Python 側と同じ順序を維持する。もし差分が発生する場合は、その理由をコメントで明記する
- コードの編集後には、必ず
poetry run task lintコマンドで、Ruff によるコードリンターと Pyright による型チェッカーを実行すること - 文字列にはシングルクォートを用いる (Docstring を除く)
- Python 3.11 の機能を使う (3.10 以下での動作は考慮不要)
- ビルトイン型を使用した Type Hint で実装する (from typing import List, Dict などは避ける)
- Pydantic モデル定義では必ず Annotated 記法を使う。
= Field()型の定義は行わずに全て Annotated 記法で定義すること - 変数・インスタンス変数は snake_case で命名する
- 関数・クラス名は UpperCamelCase で命名する (例:
class VideoEncodingTask:,def GetClientURL():)- FastAPI で定義するエンドポイントの関数名も UpperCamelCase で命名する必要がある
- FastAPI で定義するエンドポイント名は、文法的に比較的正しくなるようパス名や操作を並び替えた上で、「〇〇API」の形で命名すること
- 例: GET
/streams/live/{display_channel_id}/{quality}/mpegts->LiveMPEGTSStreamAPI - 例: PUT
/users/me->UserUpdateAPI
- 例: GET
- クラスに生えたメソッド名は lowerCamelCase で命名する (例:
LiveStream.getONAirLiveStreams()) - 複数行のコレクションには末尾カンマを含める
getattr()で型チェッカーを黙らせるのは禁止。参照する属性は型ヒントやプロパティできちんと公開し、どうしてもgetattr()が必要な場合は「その属性が必ず存在する根拠」を詳細にコメントする- すべての Docstring には Args / Returns を明記し、コメントは処理のまとまりごとに必ず加えて「なぜそうするのか」「何を意図した値なのか」を丁寧に説明する。コードを読まなくてもコメントから処理の流れを追えるようにする
- このプロジェクトでは必ずロギングモジュールとして
import loggingの代わりにfrom app import loggingを使うべき
- コードの編集後には、必ず
yarn lint; yarn typecheckコマンドで、ESLint によるコードリンターと TypeScript による型チェッカーを実行すること window.confirm()/window.alert()などのブラウザ標準ダイアログは絶対に使用しないこと。Vuetify で既存 UI と一貫したダイアログを実装する。標準ダイアログで済ませるのは妥協・甘え・ボケナス実装であり、KonomiTV の UI として許容しない- 文字列にはシングルクォートを用いる
- 新規で実装する箇所に関しては Vue 3 Composition API パターンに従う
- Vue.js 2 から移行した関係で Options API で書かれているコンポーネントがあるが、それらは Options API のまま維持する
- 新規で実装する Vue 3 Composition API のコンポーネントでは、原則として変数を lowerCamelCase で命名する
- FastAPI サーバーでは snake_case で命名している関係で外部 API のフィールドは全てスネークケースになっているが、これはそのまま参照して良い
- TypeScript による型安全性を確保する
- コンポーネント属性は可能な限り1行に記述 (約100文字まで)
- 必ず day.js を utils/index.ts からインポートして使うこと!!!new Date() を絶対に使うな!!!
- クライアント側で新たに永続化したい値が出てきた場合、
localStorage.setItem/getItemを直接呼ばず、必ずclient/src/stores/SettingsStore.tsのILocalClientSettings/ILocalClientSettingsDefaultに集約する。SettingsStore は LocalStorage への永続化と、KonomiTV アカウントによるサーバー側設定との双方向同期を一手に引き受けており、独自キーを直書きするとこの同期の枠組みから外れてしまう- DB の連番 ID に依存する値 (
selected_twitter_panel_accountなど) は環境が変わると意味を失うため、ENVIRONMENT_SPECIFIC_SETTINGS_KEYSに加えて同期無効にする - 他者の実装をレビューする際は
localStorage/sessionStorageを grep し、この集約を迂回した直接アクセスが紛れ込んでいないか必ず確認する - なお、アクセストークン (
Utils.ts)・データ放送 NVRAM エミュレーション (DataBroadcasting.vue)・DPlayer 側のキー (Jikkyo.vueのdplayer-danmaku-*) は、ユーザー設定ではなく認証状態・ハードウェアエミュレーション・サードパーティ側の永続化キーであるため、この集約ルールの対象外として既存のまま残っている
- DB の連番 ID に依存する値 (
- このプロジェクトで使用している色 (CSS 変数) などは
client/src/App.vueやclient/src/plugins/vuetify.tsに定義しているので、それを参照すること - 新規に UI を実装する際は、すでに実装されている他のコンポーネントやページの大まかなデザインの方向性を踏襲すること