API リファレンス (API Reference)
Copyright (c) 2026 Masanori Sakai
Licensed under the MIT License
概要
流星検出システムが提供するHTTP APIの完全なリファレンスです。
目次
バージョン履歴
v3.15.0 - 統計ビューに7夜ボタン追加・デフォルト期間変更
- 追加:
dashboard_templates.py— 統計期間選択に「7夜」ボタンを先頭に追加 - 変更:
dashboard_templates.py— 統計ビューのデフォルト表示期間を30夜から7夜に変更
v3.12.0 - logrotate.conf 自動生成機能追加
- 追加:
generate_compose.py—generate_logrotate_conf()関数を新規追加。日次ローテーション・90日保持・圧縮設定(compress/delaycompress/copytruncate)を生成する純粋関数 - 追加:
generate_compose.py—main()実行時にlogrotate.confを自動生成し、cron設定例を標準出力に表示
v3.11.3 - 夜別検出統計グラフ凡例カラー統一修正
- 修正:
dashboard_templates.py— 時間帯別グラフのrenderHourlyChartに全カメラ配列を渡し、cameras.indexOf(cam)でグローバルインデックスを取得するよう変更。凡例カラーが夜別グラフと一致しない問題を解消
v3.11.2 - apply_settings マスク上書きバグ修正
- 修正:
http_handlers.py—apply_settingsエンドポイントで手動更新済みマスクが上書きされるバグを修正。_is_mask_manually_modified()ヘルパーによる SHA256 ハッシュ比較ガードを追加し、generate_compose.pyと同等の保護を実現 - 追加:
tests/test_http_handlers.py—_is_mask_manually_modified()の6ケーステスト追加
v3.11.1 - ドキュメント全面改版
- 変更:
documents/配下 全リファレンスを v3.6〜v3.11 の累積変更(SQLite 化・検出エンジン責務分割・マスク自動保護・カメラ名インデックス化)に追従して改版 - 追加:
documents/DETECTION_STORE.md— 検出結果ストレージ(SQLite スキーマ・JSONL 同期アルゴリズム)の新規ドキュメント - 追加:
documents/SCRIPTS_REFERENCE.md—scripts/配下とmigrate_camera_dirs.pyの運用スクリプトリファレンスを新規作成 - 変更:
mkdocs.yml— nav にDETECTION_STORE.md/SCRIPTS_REFERENCE.mdを追加 - 変更:
README.md— ファイル構成に v3.6.2 以降の新モジュールを反映、アップグレード節に v3.6.2 / v3.10.0 / v3.11.0 を追加 - 変更:
INSTALL.md— 実在 IP を RFC5737 TEST-NET に置換(セキュリティ改善) - 変更: なし(コード無変更、ドキュメント・設定のみの改版)
v3.11.0 - カメラ名をインデックスベースに変更・既存データ移行スクリプト追加
- 変更:
generate_compose.py—CAMERA_NAMEを IP ベース(例:camera1_192_168_1_10)からインデックスベース(例:camera1)に変更。IP アドレスが変わってもカメラ名が変わらない - 変更:
dashboard_config.py— デフォルト CAMERAS 名をインデックスベースに更新 - 変更:
dashboard_routes.py— docstring 例をインデックスベース名に更新 - 追加:
migrate_camera_dirs.py— 既存の IP ベースディレクトリをcamera1/camera2/camera3に安全移行するスタンドアロンスクリプト(--dry-run/--yes対応) - 追加:
tests/test_migrate_camera_dirs.py— 移行スクリプトのユニットテスト22件
v3.10.0 - 手動更新マスクの自動保護
- 追加:
generate_compose.py— SHA256ハッシュによるマスク保護。masks/.generated_hashes.jsonにハッシュを記録し、手動更新済みマスクを再実行時に上書きしない - 追加:
generate_compose.py—--force-overwrite-masksフラグで保護対象マスクの強制上書きが可能 - 追加:
generate_compose.py— マスクあり時のみMASK_BUILD_DIR環境変数と./masks:/app/masks_buildボリュームマウントを生成 - 追加:
http_handlers.py—/confirm_mask_updateでホスト側masks/への同期書き込み(MASK_BUILD_DIR環境変数経由、パストラバーサル防止済み)
v3.9.0 - 統計ページに時間帯×カメラの検出数グラフ追加
- 追加:
dashboard_routes.py/dashboard_templates.py— 時間帯×カメラの検出数グループ棒グラフ(Plotly.js)を統計ページに追加 - 追加:
detection_store.py—compute_hourly_stats(): 5秒グリーディー重複除去後、ローカル時刻の時単位で集計 - 変更:
GET /stats_dataレスポンスにhourlyキー(時間帯別検出数)を追加
v3.8.0 - ダッシュボード UI 全面リニューアル
- 変更:
dashboard_templates.py— ライトモード移行・左固定サイドバーナビゲーション(ダーク)+アンバーアクセントボーダー導入 - 変更: フォント刷新: Orbitron(見出し)/ Inter(本文)/ JetBrains Mono(数値)
- 変更: カラーパレット刷新: ロゴタイプブルー
#1a8fc4・アンバー#d4860aを軸に再設計 - 変更: YouTube Live ボタンをカメラヘッダー内に移動。未設定カメラはグレーアウト表示
- 追加: ナビゲーションに
aria-current="page"を追加(アクセシビリティ改善)
v3.7.0 - 流星統計ビュー追加
- 追加:
dashboard_routes.py—GET /stats統計ページ(カメラ別・夜別流星数の積み重ね棒グラフ) - 追加:
dashboard_routes.py—GET /stats_data夜別流星統計 JSON API(重複除去済み) - 追加:
detection_store.py—query_detections_for_stats()統計ページ向けクエリメソッド - テスト:
tests/test_stats.py—/stats//stats_dataエンドポイント単体テスト追加
v3.6.2 - meteor_detector_rtsp_web.py リファクタリング(責務分割)
- リファクタリング:
meteor_detector_rtsp_web.py— 2,274行のモノリシック実装を4ファイルに分割(機能変更なし) - 追加:
detection_state.py— グローバル変数60個をDetectionStatedataclass に集約 - 追加:
detection_filters.py— フィルタ・パラメータ関数群(純粋関数) - 追加:
recording_manager.py— 録画ジョブ管理・ffmpegプロセス制御 - 追加:
http_handlers.py—MJPEGHandlerクラス・ThreadedHTTPServer - テスト:
tests/test_detection_state.py/tests/test_detection_filters.py追加
v3.6.1 - 移動する黒点(鳥シルエット)除外フィルタ追加
- 追加:
meteor_detector_rtsp_web.py—filter_dark_objects()関数。輝度が閾値未満のオブジェクト(鳥シルエット等)を除外 - 追加: 環境変数
BIRD_FILTER_ENABLED/BIRD_MIN_BRIGHTNESS— 通常時の黒点フィルタ設定(デフォルト無効) - 追加: 環境変数
TWILIGHT_BIRD_FILTER_ENABLED/TWILIGHT_BIRD_MIN_BRIGHTNESS— 薄明時の黒点フィルタ設定(デフォルト有効) - テスト:
tests/test_filter_dark_objects.py—filter_dark_objects()単体テスト8ケース追加
v3.6.0 - SQLite移行(パフォーマンス改善・アーキテクチャ変更)
- 追加:
detection_store.py— スレッドセーフSQLite操作モジュール(WALモード・JSONL増分同期) - 追加:
scripts/migrate_jsonl_to_sqlite.py— 既存JSONLデータのSQLite移行スクリプト - 変更:
dashboard_routes.py— 検出結果のJSONL全行読み込みをSQLiteクエリに置換。削除・ラベル操作もSQLiteベースに変更 - テスト:
tests/test_detection_store.py—detection_store単体テスト追加 - テスト:
tests/test_dashboard_routes.py— SQLite対応フィクスチャを追加
v3.5.1 - 薄明環境変数追加・CI整備・カバレッジ改善
- 修正:
generate_compose.py—TWILIGHT_DETECTION_MODE/TWILIGHT_TYPE/TWILIGHT_SENSITIVITY/TWILIGHT_MIN_SPEEDの4環境変数をコンテナテンプレートに追加(機能的欠陥の修正) - リファクタリング:
meteor_detector_rtsp_web.py— 薄明感度プリセット適用ロジックをbuild_twilight_params()ヘルパー関数に切り出し - CI:
.github/workflows/ci.yml—--cov-fail-under=70を追加しカバレッジ70%未満でCIが失敗するように設定 - CI:
.coveragercを新規作成しカバレッジ計測設定を追加 - テスト:
tests/test_generate_compose.py— 薄明環境変数の出力検証テスト追加 - テスト:
tests/test_astro_twilight.py— 朝方薄明 True 分岐カバーテスト修正 - テスト:
tests/test_meteor_detector_rtsp_web.py—TestBuildTwilightParams全フィールド検証追加
v3.4.7 - セキュリティ修正・コード品質改善・UI調整
- セキュリティ:
dashboard.py—camera_embedエンドポイントにhtml.escape()を適用し XSS を対策 - セキュリティ:
dashboard_camera_handlers.py— YouTube ストリームキーのログ漏洩を防止 - セキュリティ:
generate_compose.py— 無効URL警告ログの RTSP 認証情報をマスク - 品質:
dashboard.py—go2rtc_assetエンドポイントのエラーハンドリング追加 - 品質:
generate_compose.py— 関数内import os解消・非推奨の_replace()除去 - 品質:
pytest.ini—testpaths設定追加 - テスト: セキュリティ修正に対応するテスト5件追加
- UI:
dashboard_templates.py— 削除ボタンの表示制御変更 - 管理:
go2rtc.yamlを git 管理下から除外
v3.4.6 - 全選択UI常時表示・Dockerfile.dashboard改善
- 変更: 検出結果一覧の全選択UIを常時表示化(選択モードトグル廃止)
- 修正:
Dockerfile.dashboardの apt インストールを BuildKit cache mount 方式に変更
v3.4.5 - YouTube配信エンコーダ3段階フォールバック・Dockerfile修正
- 変更: YouTube 配信エンコーダを QSV → VAAPI → libx264 の3段階フォールバックに拡張
- Intel QSV 利用可能時:
h264_qsv720p 2000kbps - QSV 不可・VAAPI 利用可能時:
h264_vaapi720p 2000kbps(旧世代 Intel / AMD GPU) - どちらも不可:
libx264 ultrafast720p 2000kbps - 変更:
Dockerfile.dashboardをpython:3.11-slim-bookworm(Debian 12) ベースに変更(Mac 上の GPG エラー解消)
v3.4.4 - YouTube配信のマルチプラットフォーム対応
- 変更: Intel QSV ハードウェアエンコードを自動検出し、利用可能な場合は
h264_qsv、非対応環境はlibx264 ultrafastに自動フォールバック - 変更: YouTube 配信に自動再接続ループを追加(ffmpeg 終了後 15 秒待機して再接続)
- 変更: 映像解像度を 1280×720 に統一(CPU/GPU 負荷軽減)
- 変更: Dockerfile.dashboard を Ubuntu 24.04 ベースに変更
v3.3.0 - YouTube Live配信
- 追加:
POST /youtube_start/{index}— カメラのYouTube Live配信を開始 - 追加:
POST /youtube_stop/{index}— カメラのYouTube Live配信を停止 - 追加:
GET /youtube_status/{index}— カメラのYouTube Live配信状態を取得 - 追加: ダッシュボード環境変数
CAMERA{i}_YOUTUBE_KEY、CAMERA{i}_RTSP_URL、GO2RTC_API_URL - 変更: YouTube 配信停止は dashboard コンテナ内の ffmpeg プロセスを terminate/kill で終了
v3.2.5 - メンテナンスアップデート
- 変更: ダッシュボードのバージョンを 3.2.5 へ更新
- 変更: 各種ドキュメント(ARCHITECTURE.md, DETECTOR_COMPONENTS.md)を最新のコード実態に合わせて改版
v3.2.1 - 手動録画の一覧統合とサムネイル対応
- 修正: 手動録画で
pcm_alaw音声を含む RTSP を MP4 へ保存する際に失敗していた問題を修正し、映像のみ保存するよう改善 - 修正: 手動録画ファイルの探索先を各カメラ出力ディレクトリ配下
manual_recordings/...に合わせ、検出一覧カレンダーへ反映されるよう修正 - 変更: 手動録画完了後にサムネイル JPEG を自動生成し、検出一覧でプレビュー可能化
- 追加: ダッシュボードに
DELETE /manual_recording/{path}を追加し、手動録画 MP4 とサムネイルを削除可能化
v3.2.0 - カメラ別手動録画予約の追加
- 追加: ダッシュボードに
GET /camera_recording_status/{index}、POST /camera_recording_schedule/{index}、POST /camera_recording_stop/{index}を追加 - 追加: カメラ側 API に
GET /recording/status、POST /recording/schedule、POST /recording/stopを追加 - 変更:
/camera_stats/{index}と/statsのレスポンスへrecordingオブジェクトを追加し、予約状態・録画状態・保存先を取得可能化
v3.1.1 - WebRTC candidate 自動検出と生成安定化
- 変更:
generate_compose.pyがgo2rtcの candidate host を既定でローカル IP から自動検出するよう改善 - 修正: OpenCV 未導入時でも
docker-compose.yml/go2rtc.yamlの生成を継続し、マスク生成のみスキップするよう改善 - 変更: ダッシュボードおよび Docker / WebRTC 構成の関連ドキュメントを現行仕様へ改版
v3.1.0 - WebRTC ライブ表示の正式対応
- 追加: Docker 構成で
go2rtcを利用する WebRTC ライブ表示設定を正式サポート - 変更: ダッシュボードのライブ表示設定に
CAMERA*_STREAM_KIND=webrtc/CAMERA*_STREAM_URLを追加 - 変更: ダッシュボードのヘルスチェック例の
version表示を3.1.1に更新
v3.0.0 - 旧互換 MP4 正規化設定の削除
- 削除:
GET /statsのsettingsからfb_normalize/fb_delete_movを削除 - 削除: カメラ設定 API の起動時依存設定から
fb_normalize/fb_delete_movを削除 - 変更: ダッシュボードのヘルスチェック例の
version表示を3.0.0に更新
v1.24.1 - ダッシュボードの Flask アプリ運用整備
- 新規エンドポイント:
GET /health - ダッシュボードのヘルスチェックを返却
- レスポンス例:
{ "status": "ok", "version": "3.4.4", "camera_count": 3 } - 変更:
dashboard.pyを Flask のアプリファクトリ構成へ整理し、WSGI 配備やコンテナ運用時でも監視スレッドが初回リクエストで起動するよう改善
v1.24.0 - 検出 ID 管理と運用制御の拡張
- 機能追加:
/camera_settings/currentおよび/camera_settings/apply_allでdetection_enabledを扱えるようにし、全カメラの検出停止・再開を一括制御可能化 - 変更: 検出レコードの
idを、timestampに加えてstart_time/end_time/start_point/end_pointを含む現行ルールへ統一 - 運用改善: 検出データの移行・孤立ファイル救済・ディレクトリ統合を補助するメンテナンススクリプトを追加
v1.23.1 - 検出時間帯判定と状態表示の修正
- 修正: 検出時間帯の計算を「当日日没から翌日の日出まで」に是正
- 機能追加:
GET /statsレスポンスに検出状態詳細フィールドを追加 detection_status:DETECTING/OUT_OF_WINDOW/WAITING_FRAME/STREAM_LOSTdetection_window_enabled: 検出時間帯制限の有効/無効detection_window_active: 現在が検出時間帯内かdetection_window_start: 現在参照中の検出開始時刻detection_window_end: 現在参照中の検出終了時刻
v1.18.0 - 一括削除機能
- 新規エンドポイント:
POST /bulk_delete_non_meteor/{camera_name} - カメラごとに非流星検出(
label="non-meteor")を一括削除 - 削除件数とファイルリストを返却
v1.17.0 - カメラ監視機能
- 機能追加:
/camera_stats/{index}レスポンスに監視関連フィールドを追加 monitor_enabled: 監視機能の有効/無効monitor_checked_at: 最終監視確認時刻monitor_error: 監視エラーメッセージmonitor_stop_reason: 監視停止理由monitor_last_restart_at: 最終再起動時刻monitor_restart_count: 再起動回数monitor_restart_triggered: 再起動トリガー発動中か- 環境変数: カメラ監視設定(
CAMERA_MONITOR_*,CAMERA_RESTART_*)
v1.16.0 - 画面端ノイズ除外
- パラメータ追加:
exclude_edge_ratio /statsレスポンスのsettings.exclude_edge_ratio/apply_settingsリクエストボディ- 画面四辺の指定割合をノイズ除外エリアとして設定
v1.14.0 - 録画マージン設定
- パラメータ追加: 録画前後マージン
/statsレスポンスのsettings.clip_margin_before,settings.clip_margin_after/apply_settingsリクエストボディ- 検出前後に録画する追加秒数を設定可能
v1.13.0 - 全カメラ設定UI
- 新規エンドポイント:
GET /settings- 全カメラ設定ページ - 新規エンドポイント:
GET /camera_settings/current- 各カメラの現在設定取得 - 新規エンドポイント:
POST /camera_settings/apply_all- 全カメラへ設定一括適用
v1.10.0 - 検出ラベル機能
- 新規エンドポイント:
POST /detection_label - 検出に任意ラベル(
meteor,non-meteorなど)を設定 - 機能追加:
/detectionsレスポンスにlabelフィールド追加 - 機能追加:
/detections_mtimeがラベルファイル(detection_labels.json)も監視対象に(SQLite移行後はdetections.dbの更新時刻を返す)
dashboard.py API
ダッシュボードが提供するエンドポイント(デフォルトポート: 8080)
エンドポイント一覧
| エンドポイント | メソッド | 説明 |
|---|---|---|
/health |
GET | ダッシュボードヘルスチェック |
/ |
GET | ダッシュボードHTML |
/cameras |
GET | カメラライブ画面HTML |
/settings |
GET | 全カメラ設定ページ |
/detection_window |
GET | 検出時間帯取得 |
/detections |
GET | 検出一覧取得 |
/detections_mtime |
GET | 検出ログ更新時刻取得 |
/camera_embed/{index} |
GET | WebRTC 埋め込みページ |
/go2rtc_asset/{name} |
GET | go2rtc フロント資産プロキシ |
/camera_settings/current |
GET | カメラ設定の現在値取得 |
/camera_settings/apply_all |
POST | 設定を全カメラへ一括適用 |
/camera_snapshot/{index} |
GET | カメラスナップショット取得(?download=1 でDL) |
/camera_recording_status/{index} |
GET | カメラ手動録画状態取得 |
/camera_recording_schedule/{index} |
POST | カメラ手動録画の予約/即時開始 |
/camera_recording_stop/{index} |
POST | カメラ手動録画の停止 |
/camera_restart/{index} |
POST | カメラ再起動要求 |
/youtube_start/{index} |
POST | YouTube Live配信を開始 |
/youtube_stop/{index} |
POST | YouTube Live配信を停止 |
/youtube_status/{index} |
GET | YouTube Live配信状態を取得 |
/camera_stats/{index} |
GET | カメラ統計情報取得 |
/image/{camera}/{filename} |
GET | 画像ファイル取得 |
/detection/{camera}/{id} |
DELETE | 検出結果削除 |
/dashboard_stats |
GET | ダッシュボードCPU統計取得 |
/manual_recording/{path} |
DELETE | 手動録画ファイル削除 |
/bulk_delete_non_meteor/{camera_name} |
POST | カメラの非流星検出を一括削除 |
/detection_label |
POST | 検出にラベルを設定 |
/changelog |
GET | CHANGELOG表示 |
/stats |
GET | 夜別流星統計ページ(HTML) |
/stats_data |
GET | 夜別流星統計データ取得(JSON) |
GET /health
説明: ダッシュボードのプロセス状態と構成情報を返す
レスポンス:
- Content-Type: application/json
- Status: 200 OK
レスポンスボディ:
{
"status": "ok",
"version": "3.4.4",
"camera_count": 3
}
使用例:
curl http://localhost:8080/health
GET /
説明: ダッシュボードのHTMLページを返す
レスポンス:
- Content-Type: text/html; charset=utf-8
- Status: 200 OK
使用例:
curl http://localhost:8080/
GET /cameras
説明: カメラライブ表示用のHTMLページを返す
レスポンス:
- Content-Type: text/html; charset=utf-8
- Status: 200 OK
補足:
- CAMERA*_STREAM_KIND=mjpeg の場合は各カメラの /stream を直接参照
- CAMERA*_STREAM_KIND=webrtc の場合は /camera_embed/{index} を iframe で埋め込み
GET /settings
説明: 全カメラ設定UIページを返す
レスポンス:
- Content-Type: text/html; charset=utf-8
- Status: 200 OK
使用例:
curl http://localhost:8080/settings
GET /camera_embed/{index}
説明: WebRTC ライブ表示用の埋め込みHTMLを返す
レスポンス:
- Content-Type: text/html; charset=utf-8
- Status: 200 OK
補足:
- CAMERA*_STREAM_KIND=webrtc のカメラのみ有効
- 埋め込みページ内で /go2rtc_asset/video-stream.js を読み込み、go2rtc の /api/ws?src=... へ接続
- 表示モードは webrtc,mse,hls,mjpeg の優先順で自動選択
GET /go2rtc_asset/{name}
説明: go2rtc の video-stream.js / video-rtc.js をダッシュボード経由で配信
レスポンス:
- Content-Type: application/javascript; charset=utf-8
- Status: 200 OK
補足:
- WebRTC 埋め込みページからのみ利用
- Docker 内では go2rtc コンテナを名前解決し、ホスト側公開アドレスと分離して取得
GET /detection_window
説明: 天文薄暮期間(検出時間帯)を取得
クエリパラメータ:
| パラメータ | 型 | 必須 | 説明 | 例 |
|---|---|---|---|---|
lat |
float | No | 緯度 | 35.6762 |
lon |
float | No | 経度 | 139.6503 |
レスポンス:
- Content-Type: application/json
- Status: 200 OK
レスポンスボディ:
{
"start": "2026-02-01 16:45:23",
"end": "2026-02-02 06:12:45",
"enabled": true,
"latitude": 35.6762,
"longitude": 139.6503
}
フィールド説明:
| フィールド | 型 | 説明 |
|---|---|---|
start |
string | 検出開始時刻(YYYY-MM-DD HH:MM:SS) |
end |
string | 検出終了時刻(YYYY-MM-DD HH:MM:SS) |
enabled |
boolean | 時間帯制限の有効/無効 |
latitude |
float | 使用された緯度 |
longitude |
float | 使用された経度 |
エラーレスポンス:
{
"start": "",
"end": "",
"enabled": false,
"error": "meteor_detector module not available"
}
使用例:
# デフォルト座標で取得
curl "http://localhost:8080/detection_window" | jq
# 座標を指定
curl "http://localhost:8080/detection_window?lat=35.6762&lon=139.6503" | jq
# JavaScriptから取得
fetch('/detection_window?lat=35.6762&lon=139.6503')
.then(r => r.json())
.then(data => console.log(data));
GET /detections
説明: 全カメラの検出結果一覧を取得
レスポンス:
- Content-Type: application/json
- Status: 200 OK
レスポンスボディ:
{
"total": 15,
"recent": [
{
"id": "det_a1b2c3d4e5f6g7h8i9j0",
"time": "2026-02-02 06:55:33",
"camera": "camera1",
"camera_display": "東側",
"confidence": "87%",
"image": "camera1/meteor_20260202_065533_composite.jpg",
"mp4": "camera1/meteor_20260202_065533.mp4",
"composite_original": "camera1/meteor_20260202_065533_composite_original.jpg",
"alternate_clip_paths": [
"camera1/meteor_20260202_065533.mov"
],
"label": "",
"source_type": ""
},
{
"id": "det_b2c3d4e5f6g7h8i9j0a1",
"time": "2026-02-02 05:32:18",
"camera": "camera2",
"camera_display": "南側",
"confidence": "92%",
"image": "camera2/meteor_20260202_053218_composite.jpg",
"mp4": "camera2/meteor_20260202_053218.mp4",
"composite_original": "camera2/meteor_20260202_053218_composite_original.jpg",
"alternate_clip_paths": [],
"label": "meteor",
"source_type": ""
}
]
}
フィールド説明:
| フィールド | 型 | 説明 |
|---|---|---|
total |
integer | 総検出数(論理削除済みを除く) |
recent |
array | 検出リスト(時刻降順) |
recent[].id |
string | SHA-1 ダイジェストベースの検出ID(det_ + 先頭20桁)。削除・ラベル API の URL パラメータに使用 |
recent[].time |
string | 検出時刻(ローカルタイム) |
recent[].camera |
string | カメラ内部名(v3.11.0+ は camera{i}) |
recent[].camera_display |
string | カメラの表示名(CAMERA{i}_NAME_DISPLAY)。未設定時は camera と同じ内部名(例: camera1)が入る |
recent[].confidence |
string | 信頼度(パーセント表示) |
recent[].image |
string | コンポジット画像パス(カメラディレクトリ相対) |
recent[].mp4 |
string | 動画パス(通常は .mp4) |
recent[].composite_original |
string | 元画像の比較明合成パス |
recent[].alternate_clip_paths |
array | 同一イベントに紐づく追加クリップパス(例: .mov 併存時)。未登録時は空配列 |
recent[].label |
string | 検出ラベル(v1.10.0+、未設定時は空文字) |
recent[].source_type |
string | 手動録画エントリの場合 "manual_recording"、通常検出は空文字(v3.2.1+) |
!!! note "備考"
レスポンスは detection_store.query_detections()(deleted = 0 のみ)の結果を整形して返します。alternate_clip_paths は SQLite 上では JSON 文字列として保存され、読み出し時に配列へデコードされます。詳細は DETECTION_STORE.md を参照してください。
使用例:
# curlで取得
curl http://localhost:8080/detections | jq
# 総検出数のみ取得
curl -s http://localhost:8080/detections | jq '.total'
# 最新の検出のみ取得
curl -s http://localhost:8080/detections | jq '.recent[0]'
# JavaScriptから取得
fetch('/detections')
.then(r => r.json())
.then(data => {
console.log('Total:', data.total);
data.recent.forEach(d => console.log(d.time, d.camera));
});
GET /detections_mtime
説明: $DETECTIONS_DIR/detections.db の更新時刻(UNIXエポック秒)を取得
レスポンス:
- Content-Type: application/json
- Status: 200 OK
レスポンスボディ:
{
"mtime": 1751461442.123
}
使用例:
curl http://localhost:8080/detections_mtime | jq
fetch('/detections_mtime')
.then(r => r.json())
.then(data => console.log('mtime:', data.mtime));
GET /camera_stats/{index}
説明: 指定カメラの統計情報を取得(v1.17.0で監視機能を追加、v3.2.0で recording フィールドを追加)
URLパラメータ:
| パラメータ | 型 | 説明 | 例 |
|---|---|---|---|
index |
integer | カメラインデックス(0始まり) | 0 |
レスポンス:
- Content-Type: application/json
- Status: 200 OK
レスポンスボディ:
{
"detections": 5,
"elapsed": 3600.5,
"camera": "camera1",
"stream_alive": true,
"time_since_last_frame": 0.03,
"is_detecting": true,
"monitor_enabled": true,
"monitor_checked_at": "2026-02-24 12:34:56",
"monitor_error": null,
"monitor_stop_reason": null,
"monitor_last_restart_at": "2026-02-24 10:00:00",
"monitor_restart_count": 2,
"monitor_restart_triggered": false
}
フィールド説明:
| フィールド | 型 | 説明 |
|---|---|---|
detections |
integer | 検出数 |
elapsed |
float | 稼働時間(秒) |
camera |
string | カメラ名 |
stream_alive |
boolean | ストリーム生存確認 |
time_since_last_frame |
float | 最終フレームからの経過時間(秒) |
is_detecting |
boolean | 現在検出処理中か |
monitor_enabled |
boolean | カメラ監視機能の有効/無効 |
monitor_checked_at |
string/null | 最終監視確認時刻 |
monitor_error |
string/null | 監視エラーメッセージ |
monitor_stop_reason |
string/null | 監視停止理由 |
monitor_last_restart_at |
string/null | 最終再起動時刻 |
monitor_restart_count |
integer | 再起動回数 |
monitor_restart_triggered |
boolean | 再起動トリガー発動中か |
recording |
object/null | 手動録画状態(v3.2.0以降、カメラが対応している場合に含まれる) |
使用例:
# curlで取得
curl http://localhost:8080/camera_stats/0 | jq
# JavaScriptから定期取得
setInterval(() => {
fetch('/camera_stats/0')
.then(r => r.json())
.then(data => {
console.log('Stream alive:', data.stream_alive);
console.log('Monitor enabled:', data.monitor_enabled);
console.log('Restart count:', data.monitor_restart_count);
});
}, 5000);
GET /image/{camera}/{filename}
説明: 検出画像ファイルを取得
URLパラメータ:
| パラメータ | 型 | 説明 | 例 |
|---|---|---|---|
camera |
string | カメラディレクトリ名 | camera1 |
filename |
string | ファイル名 | meteor_20260202_065533_composite.jpg |
レスポンス:
- Content-Type: image/jpeg または image/png
- Status: 200 OK
- Body: バイナリ画像データ
エラーレスポンス: - Status: 404 Not Found
使用例:
# 画像をダウンロード
curl -O "http://localhost:8080/image/camera1/meteor_20260202_065533_composite.jpg"
# HTMLから表示
<img src="/image/camera1/meteor_20260202_065533_composite.jpg" alt="Meteor">
# ダウンロードリンク
<a href="/image/camera1/meteor_20260202_065533_composite.jpg" download>
Download Image
</a>
GET /camera_snapshot/{index}
説明: 指定カメラの現在フレームをJPEGで取得
URLパラメータ:
| パラメータ | 型 | 説明 | 例 |
|---|---|---|---|
index |
integer | カメラインデックス(0始まり) | 0 |
クエリパラメータ:
| パラメータ | 型 | 必須 | 説明 | 例 |
|---|---|---|---|---|
download |
string | No | 1/true/yes で Content-Disposition: attachment を付与 |
1 |
レスポンス:
- Content-Type: image/jpeg
- Status: 200 OK
使用例:
# 画像を直接表示
curl "http://localhost:8080/camera_snapshot/0" --output snapshot.jpg
# ダウンロード用途(attachmentヘッダ付き)
curl -OJ "http://localhost:8080/camera_snapshot/0?download=1"
GET /camera_recording_status/{index}
説明: 指定カメラの手動録画状態を取得
レスポンス:
- Content-Type: application/json
- Status: 200 OK
レスポンスボディ:
{
"success": true,
"recording": {
"supported": true,
"state": "idle",
"camera": "camera1",
"job_id": "",
"start_at": "",
"scheduled_at": "",
"started_at": "",
"ended_at": "",
"duration_sec": 0,
"remaining_sec": 0,
"output_path": "",
"error": ""
}
}
DELETE /manual_recording/{path}
説明: 手動録画の MP4 と対応するサムネイル JPEG を削除
補足:
- 対象は manual_recordings 配下の .mp4 のみ
- 同名 .jpg があれば合わせて削除
使用例:
curl -X DELETE "http://localhost:8080/manual_recording/camera1/manual_recordings/camera1/manual_camera1_20260319_213000_90s.mp4" | jq
POST /camera_recording_schedule/{index}
説明: 指定カメラの手動録画を予約または即時開始
リクエストボディ:
{
"start_at": "2026-03-19T21:30:00",
"duration_sec": 90
}
補足:
- start_at を空文字または省略すると即時開始
- duration_sec は 1 以上 86400 以下
使用例:
curl -X POST "http://localhost:8080/camera_recording_schedule/0" \
-H "Content-Type: application/json" \
-d '{"start_at":"2026-03-19T21:30:00","duration_sec":90}' | jq
POST /camera_recording_stop/{index}
説明: 予約中または録画中の手動録画を停止
レスポンス:
- Content-Type: application/json
- Status: 200 OK
使用例:
curl -X POST "http://localhost:8080/camera_recording_stop/0" | jq
POST /camera_restart/{index}
説明: 指定カメラに再起動を要求(非同期)
URLパラメータ:
| パラメータ | 型 | 説明 | 例 |
|---|---|---|---|
index |
integer | カメラインデックス(0始まり) | 1 |
レスポンス:
- Content-Type: application/json
- Status: 202 Accepted
レスポンスボディ:
{
"success": true,
"message": "restart requested"
}
使用例:
curl -X POST "http://localhost:8080/camera_restart/1" | jq
POST /youtube_start/{index}
説明: 指定カメラのYouTube Live配信を開始する。dashboard コンテナ内で ffmpeg サブプロセスを起動し、go2rtc の RTSP リレー経由で YouTube へ直接 RTMP 送信する。
URLパラメータ:
| パラメータ | 型 | 説明 | 例 |
|---|---|---|---|
index |
integer | カメラインデックス(0始まり) | 2 |
前提条件: 対象カメラに CAMERA{i}_YOUTUBE_KEY が設定されていること
レスポンス:
- Content-Type: application/json
- Status: 200 OK / 400 Bad Request / 502 Bad Gateway
{"success": true}
使用例:
curl -X POST "http://localhost:8080/youtube_start/2" | jq
POST /youtube_stop/{index}
説明: 指定カメラのYouTube Live配信を停止する。dashboard コンテナ内の ffmpeg サブプロセスを terminate/kill で確実に終了する。
URLパラメータ:
| パラメータ | 型 | 説明 | 例 |
|---|---|---|---|
index |
integer | カメラインデックス(0始まり) | 2 |
レスポンス:
- Content-Type: application/json
- Status: 200 OK / 400 Bad Request / 502 Bad Gateway
{"success": true}
使用例:
curl -X POST "http://localhost:8080/youtube_stop/2" | jq
GET /youtube_status/{index}
説明: 指定カメラのYouTube Live配信状態を取得する。dashboard コンテナ内の ffmpeg サブプロセスの生死を確認。
URLパラメータ:
| パラメータ | 型 | 説明 | 例 |
|---|---|---|---|
index |
integer | カメラインデックス(0始まり) | 2 |
レスポンス:
- Content-Type: application/json
- Status: 200 OK
{"streaming": true}
使用例:
curl "http://localhost:8080/youtube_status/2" | jq
GET /camera_settings/current
説明: 各カメラの /stats.settings を取得し、設定ページ表示用に返す
レスポンス:
- Content-Type: application/json
- Status: 200 OK
レスポンスボディ(例):
{
"success": true,
"settings": {
"diff_threshold": 20,
"nuisance_overlap_threshold": 0.6
},
"results": [
{
"camera": "camera1",
"success": true,
"settings": {
"diff_threshold": 20
}
}
],
"ok_count": 1,
"total": 1
}
使用例:
curl -s http://localhost:8080/camera_settings/current | jq
POST /camera_settings/apply_all
説明: 指定した設定値を全カメラへ一括適用(各カメラの POST /apply_settings を呼び出し)
リクエストボディ(例):
{
"diff_threshold": 20,
"min_brightness": 180,
"nuisance_overlap_threshold": 0.60,
"nuisance_path_overlap_threshold": 0.70,
"min_track_points": 4,
"max_stationary_ratio": 0.40
}
レスポンス:
- Content-Type: application/json
- Status: 200 OK
レスポンスボディ(例):
{
"success": true,
"applied_count": 3,
"total": 3,
"results": [
{
"camera": "camera1",
"success": true,
"response": {
"success": true,
"restart_required": true,
"restart_requested": true,
"restart_triggers": ["sensitivity", "scale"]
}
},
{ "camera": "camera2", "success": true, "response": { "success": true } },
{ "camera": "camera3", "success": false, "error": "timeout" }
]
}
使用例:
curl -X POST "http://localhost:8080/camera_settings/apply_all" \
-H "Content-Type: application/json" \
-d '{"diff_threshold":20,"nuisance_overlap_threshold":0.60}' | jq
DELETE /detection/{camera}/{id}
説明: 検出結果を論理削除する。SQLite 上では detections.deleted = 1 フラグを立て、他の検出から参照されていないメディアファイル(MP4 / JPG / alternate clips)のみ物理削除する。JSONL ファイルは変更しない(ロールバック用に保持)
削除ロジック(v3.6.0+):
detection_store.get_detection_by_id(id)で対象レコードを取得- 各アセットパス(
clip_path/image_path/composite_original_path/alternate_clip_paths)についてcount_asset_references(exclude_id=id)で参照数を数え、残存参照が 0 のパスのみファイルシステムから削除 soft_delete(id)でdeleted = 1を立てる- 削除したファイル名のリストを返却
JSONL ファイルは変更されないため、論理削除の取り消しは detections.db を削除して scripts/migrate_jsonl_to_sqlite.py を再実行すれば復元できます。詳しい手順は DETECTION_STORE.md のトラブルシューティング節 を参照してください。
URLパラメータ:
| パラメータ | 型 | 説明 | 例 |
|---|---|---|---|
camera |
string | カメラディレクトリ名 | camera1 |
id |
string | 検出ID(/detections レスポンスの id フィールド) |
det_a1b2c3d4e5f6789012 |
レスポンス:
- Content-Type: application/json
- Status: 200 OK
成功レスポンス:
{
"success": true,
"id": "det_a1b2c3d4e5f6789012",
"deleted_files": [
"meteor_20260202_065533.mp4",
"meteor_20260202_065533_composite.jpg",
"meteor_20260202_065533_composite_original.jpg"
],
"message": "3個のファイルを削除しました"
}
エラーレスポンス:
{
"success": false,
"error": "detection id not found: camera1 det_a1b2c3d4e5f6789012"
}
使用例:
# curlで削除(idは/detectionsで取得)
curl -X DELETE "http://localhost:8080/detection/camera1/det_a1b2c3d4e5f6789012"
# JavaScriptから削除
fetch(`/detection/camera1/${detectionId}`, {
method: 'DELETE'
})
.then(r => r.json())
.then(data => {
if (data.success) {
alert(data.message);
} else {
alert('削除失敗: ' + data.error);
}
});
GET /dashboard_stats
説明: ダッシュボードプロセスの CPU 使用率を取得
レスポンス:
- Content-Type: application/json
- Status: 200 OK
レスポンスボディ:
{
"cpu_percent": 12.3
}
フィールド説明:
| フィールド | 型 | 説明 |
|---|---|---|
cpu_percent |
float | CPU使用率(%、0.0〜100.0) |
使用例:
curl http://localhost:8080/dashboard_stats | jq
POST /bulk_delete_non_meteor/{camera_name}
説明: 指定カメラの「それ以外」検出(ラベルが post_detected の検出)を一括削除(v1.18.0)
URLパラメータ:
| パラメータ | 型 | 説明 | 例 |
|---|---|---|---|
camera_name |
string | カメラディレクトリ名 | camera1 |
レスポンス:
- Content-Type: application/json
- Status: 200 OK
成功レスポンス:
{
"success": true,
"deleted_count": 5,
"deleted_detections": [
{
"time": "2026-02-02 06:55:33",
"deleted_files": [
"meteor_20260202_065533.mp4",
"meteor_20260202_065533_composite.jpg",
"meteor_20260202_065533_composite_original.jpg"
]
},
{
"time": "2026-02-02 05:32:18",
"deleted_files": [
"meteor_20260202_053218.mp4",
"meteor_20260202_053218_composite.jpg",
"meteor_20260202_053218_composite_original.jpg"
]
}
],
"message": "5件の非流星検出を削除しました(合計15ファイル)"
}
エラーレスポンス:
{
"success": false,
"error": "camera directory not found: camera1"
}
使用例:
# curlで一括削除
curl -X POST "http://localhost:8080/bulk_delete_non_meteor/camera1" | jq
# JavaScriptから一括削除
fetch('/bulk_delete_non_meteor/camera1', {
method: 'POST'
})
.then(r => r.json())
.then(data => {
if (data.success) {
alert(`${data.deleted_count}件の「それ以外」検出を削除しました`);
} else {
alert('削除失敗: ' + data.error);
}
});
POST /detection_label
説明: 検出にラベルを設定(v1.10.0)
リクエストボディ:
{
"camera": "camera1",
"id": "det_a1b2c3d4e5f6789012",
"label": "detected"
}
パラメータ説明:
| パラメータ | 型 | 必須 | 説明 | 例 |
|---|---|---|---|---|
camera |
string | Yes | カメラディレクトリ名 | camera1 |
id |
string | Yes | 検出ID(/detections レスポンスの id フィールド) |
det_a1b2c3d4e5f6789012 |
label |
string | Yes | ラベル(detected または post_detected) |
detected |
レスポンス:
- Content-Type: application/json
- Status: 200 OK
成功レスポンス:
{
"success": true,
"message": "Label updated"
}
エラーレスポンス:
{
"success": false,
"error": "Detection not found"
}
使用例:
# curlでラベル設定
curl -X POST "http://localhost:8080/detection_label" \
-H "Content-Type: application/json" \
-d '{
"camera": "camera1",
"id": "det_a1b2c3d4e5f6789012",
"label": "detected"
}' | jq
# JavaScriptからラベル設定
fetch('/detection_label', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
camera: 'camera1',
id: detectionId,
label: 'detected'
})
})
.then(r => r.json())
.then(data => {
if (data.success) {
alert('ラベル設定完了');
} else {
alert('エラー: ' + data.error);
}
});
ラベルの活用:
- detected: 流星として確認済み
- post_detected: 自動検出後に「それ以外」と判定されたもの(bulk_delete_non_meteor の削除対象)
GET /changelog
説明: CHANGELOG.mdの内容を取得
レスポンス:
- Content-Type: text/plain; charset=utf-8
- Status: 200 OK
- Body: CHANGELOG.mdの内容(テキスト)
使用例:
curl http://localhost:8080/changelog
GET /stats
説明: カメラ別・夜別(日没〜翌日の日の出)の流星統計を表示するHTMLページ。
ナビゲーションバーの「統計」タブから遷移する。データは /stats_data から非同期取得する。
初期表示は過去7夜分のデータ(ページロード時に days=7 で /stats_data を呼び出す)。
レスポンス:
- Content-Type: text/html; charset=utf-8
- Status: 200 OK
使用例:
http://localhost:8080/stats
GET /stats_data
説明: カメラ別・夜別(日没〜翌日の日の出)の流星統計をJSONで返す。 複数カメラが5秒以内に同一流星を検出した場合は重複除去し1件として集計する。
クエリパラメータ:
| パラメータ | 型 | デフォルト | 説明 |
|-----------|-----|---------|------|
| days | integer | 30 | 遡る日数(1〜365) |
レスポンス:
- Content-Type: application/json
- Status: 200 OK
{
"nights": [
{
"date": "2026-04-12",
"sunset": "2026-04-12 18:30",
"sunrise": "2026-04-13 05:20",
"total": 7,
"by_camera": {
"East": 4,
"South": 3,
"West": 2
},
"duplicates": 2,
"ongoing": false,
"night_hours": 10.83
}
],
"cameras": ["East", "South", "West"],
"total_events": 73,
"hourly": {
"hours": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23],
"cameras": ["East", "South", "West"],
"by_hour": {
"East": [0, 0, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 2, 1],
"South": [0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 1, 0],
"West": [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0]
}
}
}
| フィールド | 説明 |
|---|---|
nights |
夜ごとの集計(新しい順) |
nights[].date |
日没側の日付(YYYY-MM-DD) |
nights[].total |
重複除去後の流星数 |
nights[].by_camera |
カメラ表示名ごとの検出数 |
nights[].duplicates |
重複として除去した件数 |
nights[].ongoing |
現在進行中の夜(日の出前)の場合 true |
nights[].night_hours |
日没から日の出までの時間数(時間単位・小数2桁) |
cameras |
カメラ表示名一覧 |
total_events |
全期間の流星総数(重複除去後) |
hourly |
時間帯別集計(集計期間全体の合算) |
hourly.hours |
時刻一覧(0〜23の固定配列) |
hourly.cameras |
カメラ表示名一覧 |
hourly.by_hour |
カメラ表示名ごとの時刻別検出数(インデックス=時刻) |
使用例:
# 過去30日分を取得
curl http://localhost:8080/stats_data
# 過去90日分を取得
curl "http://localhost:8080/stats_data?days=90"
meteor_detector_rtsp_web.py API
各カメラコンテナが提供するエンドポイント(デフォルトポート: 8080)
エンドポイント一覧
| エンドポイント | メソッド | 説明 |
|---|---|---|
/ |
GET | プレビューHTML |
/stream |
GET | MJPEGストリーム |
/snapshot |
GET | 現在フレームJPEG |
/stats |
GET | 統計情報 |
/recording/status |
GET | 手動録画状態取得 |
/recording/schedule |
POST | 手動録画の予約/即時開始 |
/recording/stop |
POST | 手動録画の停止 |
/update_mask |
POST | 現在フレームからマスク再生成 |
/apply_settings |
POST | 設定値をランタイム反映 |
/restart |
POST | プロセス再起動要求 |
GET /
説明: カメラプレビューのHTMLページを返す
レスポンス:
- Content-Type: text/html; charset=utf-8
- Status: 200 OK
使用例:
# camera1のプレビュー
curl http://localhost:8081/
# ブラウザで開く
open http://localhost:8081/
GET /stream
説明: MJPEGストリーム(Motion JPEG)を返す
補足:
- 各カメラコンテナ (meteor_detector_rtsp_web.py) が提供するライブ表示用エンドポイント
- ダッシュボードが mjpeg 構成のときはブラウザがこのURLを直接参照
- ダッシュボードが webrtc 構成のときは、ライブ表示の主経路は go2rtc 経由になり、この /stream は主表示には使われません
レスポンス:
- Content-Type: multipart/x-mixed-replace; boundary=frame
- Status: 200 OK
- Body: 連続的なJPEGフレーム(約30fps)
ストリームフォーマット:
--frame\r\n
Content-Type: image/jpeg\r\n\r\n
<JPEG binary data>
\r\n
--frame\r\n
Content-Type: image/jpeg\r\n\r\n
<JPEG binary data>
\r\n
...
使用例:
# HTMLで表示
<img src="http://localhost:8081/stream" alt="Live Stream">
# VLCで再生
vlc http://localhost:8081/stream
# ffmpegで録画
ffmpeg -i http://localhost:8081/stream -t 60 output.mp4
特徴: - リアルタイムプレビュー - 検出中の物体が緑丸で表示 - 追跡中の軌跡が黄線で表示 - 流星検出時に赤で表示 - フレームレート: 約30fps - 画質: JPEG品質70%
GET /stats
説明: カメラの統計情報を取得
レスポンス:
- Content-Type: application/json
- Status: 200 OK
レスポンスボディ:
{
"detections": 5,
"elapsed": 3600.5,
"camera": "camera1",
"settings": {
"sensitivity": "medium",
"scale": 0.5,
"buffer": 15.0,
"extract_clips": true,
"source_fps": 20.0,
"exclude_bottom": 0.0625,
"exclude_bottom_ratio": 0.0625,
"mask_image": "/app/mask_image.png",
"mask_from_day": "",
"mask_dilate": 5,
"nuisance_mask_image": "",
"nuisance_from_night": "",
"nuisance_dilate": 3,
"nuisance_overlap_threshold": 0.6
},
"runtime_fps": 19.83,
"stream_alive": true,
"time_since_last_frame": 0.03,
"is_detecting": true,
"detection_status": "DETECTING",
"detection_window_enabled": true,
"detection_window_active": true,
"detection_window_start": "2026-03-09 17:46:53",
"detection_window_end": "2026-03-10 06:03:38",
"twilight_active": false,
"twilight_detection_mode": "reduce",
"twilight_type": "nautical",
"recording": {
"supported": true,
"state": "scheduled",
"start_at": "2026-03-19T21:30:00+09:00",
"duration_sec": 90,
"remaining_sec": 42,
"output_path": "/output/manual_recordings/camera1/manual_camera1_20260319_213000_90s.mp4",
"error": ""
}
}
フィールド説明:
| フィールド | 型 | 説明 |
|---|---|---|
detections |
integer | 検出数 |
elapsed |
float | 稼働時間(秒) |
camera |
string | カメラ名 |
settings |
object | 設定情報 |
settings.sensitivity |
string | 感度プリセット |
settings.scale |
float | 処理スケール |
settings.buffer |
float | バッファ秒数 |
settings.extract_clips |
boolean | 動画保存の有効/無効 |
settings.source_fps |
float | 接続時に取得した入力ストリームFPS |
settings.exclude_bottom |
float | 画面下部除外率 |
settings.exclude_bottom_ratio |
float | 画面下部除外率(内部キー) |
settings.exclude_edge_ratio |
float | 画面端ノイズ除外率(v1.16.0、デフォルト0.0) |
settings.clip_margin_before |
float | 録画前マージン秒数(v1.14.0、デフォルト0.5) |
settings.clip_margin_after |
float | 録画後マージン秒数(v1.14.0、デフォルト0.5) |
settings.mask_image |
string | マスク画像(優先) |
settings.mask_from_day |
string | 昼間画像から生成するマスク |
settings.mask_dilate |
integer | マスク拡張ピクセル数 |
settings.nuisance_overlap_threshold |
float | ノイズ帯重なり閾値 |
settings.nuisance_path_overlap_threshold |
float | ノイズ帯経路重なり閾値 |
settings.min_track_points |
integer | 最小追跡点数 |
settings.max_stationary_ratio |
float | 静止率上限 |
settings.small_area_threshold |
integer | 小領域判定閾値 |
settings.nuisance_mask_image |
string | ノイズ帯マスク画像 |
settings.nuisance_from_night |
string | 夜間画像からのノイズ帯生成元 |
settings.nuisance_dilate |
integer | ノイズ帯マスク拡張ピクセル数 |
runtime_fps |
float | 直近フレームから算出した実効FPS |
stream_alive |
boolean | ストリーム生存確認 |
time_since_last_frame |
float | 最終フレームからの経過時間(秒) |
is_detecting |
boolean | 現在検出処理中か |
detection_status |
string | 検出状態詳細。DETECTING / OUT_OF_WINDOW / TWILIGHT_SKIP / WAITING_FRAME / STREAM_LOST |
detection_window_enabled |
boolean | 検出時間帯制限が有効か |
detection_window_active |
boolean | 現在が検出時間帯内か |
detection_window_start |
string | 現在参照中の検出開始時刻 |
detection_window_end |
string | 現在参照中の検出終了時刻 |
twilight_active |
boolean | 現在が薄明期間内か |
twilight_detection_mode |
string | 薄明時の検出モード。reduce(感度低下)/ skip(検出停止) |
twilight_type |
string | 薄明種別。civil / nautical / astronomical |
recording |
object | 手動録画状態 |
recording.supported |
boolean | 手動録画機能が利用可能か |
recording.state |
string | idle / scheduled / recording / completed / failed / stopped |
recording.start_at |
string | 予約開始時刻 |
recording.duration_sec |
integer | 録画予定秒数 |
recording.remaining_sec |
integer | 残り秒数 |
recording.output_path |
string | 保存先MP4パス |
recording.error |
string | 失敗・停止理由 |
使用例:
# curlで取得
curl http://localhost:8081/stats | jq
# 検出数のみ取得
curl -s http://localhost:8081/stats | jq '.detections'
# ストリーム状態を確認
curl -s http://localhost:8081/stats | jq '.stream_alive'
# 全カメラの統計を一括取得
for port in 8081 8082 8083; do
echo "Port $port:"
curl -s "http://localhost:$port/stats" | jq '{camera, detections, stream_alive}'
done
# JavaScriptから定期取得
setInterval(() => {
fetch('http://localhost:8081/stats')
.then(r => r.json())
.then(data => {
console.log('Detections:', data.detections);
console.log('Stream alive:', data.stream_alive);
console.log('Is detecting:', data.is_detecting);
});
}, 2000); // 2秒ごと
GET /recording/status
説明: カメラコンテナ内の手動録画状態を取得
レスポンス:
- Content-Type: application/json
- Status: 200 OK
レスポンスボディ:
{
"success": true,
"recording": {
"supported": true,
"state": "recording",
"camera": "camera1",
"job_id": "rec_1742387400000",
"start_at": "2026-03-19T21:30:00+09:00",
"scheduled_at": "2026-03-19T21:29:10+09:00",
"started_at": "2026-03-19T21:30:00+09:00",
"ended_at": "",
"duration_sec": 90,
"remaining_sec": 37,
"output_path": "/output/manual_recordings/camera1/manual_camera1_20260319_213000_90s.mp4",
"error": ""
}
}
POST /recording/schedule
説明: RTSP入力を ffmpeg で MP4 へ保存する手動録画ジョブを予約または即時開始
リクエストボディ:
{
"start_at": "2026-03-19T21:30:00",
"duration_sec": 90
}
補足:
- start_at 省略時は即時開始
- 既に scheduled または recording のジョブがある場合は失敗
- 保存先は manual_recordings/<camera>/ 配下
POST /recording/stop
説明: 予約中または録画中の手動録画ジョブを停止
レスポンス:
- Content-Type: application/json
- Status: 200 OK
GET /snapshot
説明: 現在フレームをJPEGで取得
レスポンス:
- Content-Type: image/jpeg
- Status: 200 OK
使用例:
curl "http://localhost:8081/snapshot" --output camera1_snapshot.jpg
POST /update_mask
説明: 現在フレームから除外マスクを再生成して即時反映(固定カメラ向け)
レスポンス:
- Content-Type: application/json
- Status: 200 OK
レスポンスボディ:
{
"success": true,
"message": "mask updated",
"saved": "/output/masks/camera1_mask.png"
}
フィールド説明:
| フィールド | 型 | 説明 |
|---|---|---|
success |
boolean | 更新成功/失敗 |
message |
string | 結果メッセージ |
saved |
string | 保存先(永続化ファイル) |
使用例:
# マスク更新
curl -X POST http://localhost:8081/update_mask | jq
POST /apply_settings
説明: 設定をランタイム反映。起動時依存の項目は設定保存後に自動再起動を要求
リクエストボディ(例):
{
"sensitivity": "medium",
"scale": 0.5,
"buffer": 15,
"extract_clips": true,
"diff_threshold": 20,
"min_brightness": 180,
"min_linearity": 0.7,
"nuisance_overlap_threshold": 0.6,
"nuisance_path_overlap_threshold": 0.7,
"min_track_points": 4,
"max_stationary_ratio": 0.4,
"small_area_threshold": 40,
"mask_dilate": 20,
"nuisance_dilate": 3,
"exclude_edge_ratio": 0.0,
"clip_margin_before": 0.5,
"clip_margin_after": 0.5
}
レスポンスボディ(例):
{
"success": true,
"applied": {
"sensitivity": "medium",
"scale": 0.5,
"diff_threshold": 20
},
"errors": [],
"restart_required": true,
"restart_requested": true,
"restart_triggers": ["sensitivity", "scale"]
}
反映ルール:
- 再起動不要: diff_threshold など検出ロジック/誤検出抑制の閾値群
- 自動再起動で反映: sensitivity / scale / buffer / extract_clips
- 設定は output/runtime_settings/<camera>.json に永続化され、再起動後も維持
使用例:
curl -X POST http://localhost:8081/apply_settings \
-H "Content-Type: application/json" \
-d '{"diff_threshold":20,"nuisance_overlap_threshold":0.60}' | jq
POST /restart
説明: カメラプロセスへ再起動を要求(Dockerの restart: unless-stopped 運用を想定)
レスポンス:
- Content-Type: application/json
- Status: 202 Accepted
レスポンスボディ:
{
"success": true,
"message": "restart requested"
}
使用例:
curl -X POST http://localhost:8081/restart | jq
共通仕様
CORS(Cross-Origin Resource Sharing)
現在の設定:
# /stats /update_mask /apply_settings /restart はCORS許可
self.send_header('Access-Control-Allow-Origin', '*')
制限事項: - 他のエンドポイントはCORS未対応 - 外部ドメインからのアクセスは制限される
カスタマイズ例:
# すべてのエンドポイントでCORS許可(セキュリティ注意)
def end_headers(self):
self.send_header('Access-Control-Allow-Origin', '*')
self.send_header('Access-Control-Allow-Methods', 'GET, POST, DELETE')
self.send_header('Access-Control-Allow-Headers', 'Content-Type')
BaseHTTPRequestHandler.end_headers(self)
レート制限
現在の制限: なし
推奨実装 (Nginxリバースプロキシ):
# /etc/nginx/sites-available/meteor
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
server {
location /api/ {
limit_req zone=api burst=20;
proxy_pass http://localhost:8080/;
}
}
タイムアウト
| エンドポイント | タイムアウト | 理由 |
|---|---|---|
/stream |
なし | ストリーミング |
| その他 | 30秒 | ブラウザデフォルト |
環境変数
カメラ監視機能(v1.17.0)
カメラの自動監視と再起動を制御する環境変数です。
| 環境変数 | デフォルト | 説明 |
|---|---|---|
CAMERA_MONITOR_ENABLED |
true |
カメラ監視機能の有効/無効 |
CAMERA_MONITOR_INTERVAL |
2.0 |
監視チェック間隔(秒) |
CAMERA_MONITOR_TIMEOUT |
6.0 |
カメラ統計取得タイムアウト(秒) |
CAMERA_RESTART_TIMEOUT |
5.0 |
再起動リクエストタイムアウト(秒) |
CAMERA_RESTART_COOLDOWN_SEC |
120 |
再起動後のクールダウン時間(秒) |
CAMERA_MONITOR_FAIL_THRESHOLD |
12 |
統計取得失敗が連続でこの回数に達すると再起動を試みる |
DETECTION_MONITOR_INTERVAL |
2.0 |
検出キャッシュ更新間隔(秒) |
使用例(docker-compose.yml):
services:
dashboard:
image: meteor-dashboard:latest
environment:
- CAMERA_MONITOR_ENABLED=true
- CAMERA_MONITOR_INTERVAL=2.0
- CAMERA_MONITOR_TIMEOUT=6.0
- CAMERA_RESTART_TIMEOUT=5.0
- CAMERA_RESTART_COOLDOWN_SEC=120
- CAMERA_MONITOR_FAIL_THRESHOLD=12
ports:
- "8080:8080"
監視機能の動作:
1. 各カメラの /stats エンドポイントを定期的に確認
2. time_since_last_frame が CAMERA_MONITOR_TIMEOUT を超えた場合、フレーム停止と判定
3. CAMERA_RESTART_ENABLED=true の場合、自動的に /restart を呼び出し
4. 再起動回数が CAMERA_RESTART_MAX_COUNT を超えると監視を停止
5. 監視状態は /camera_stats/{index} で確認可能
監視を無効化する場合:
environment:
- CAMERA_MONITOR_ENABLED=false
エラーコード
HTTPステータスコード
| コード | 説明 | 発生条件 |
|---|---|---|
| 200 | OK | 成功 |
| 404 | Not Found | ファイルまたはエンドポイントが存在しない |
| 500 | Internal Server Error | サーバー内部エラー |
エラーレスポンス例
{
"success": false,
"error": "File not found"
}
使用例
Python
import requests
# 検出一覧を取得
response = requests.get('http://localhost:8080/detections')
data = response.json()
print(f"Total detections: {data['total']}")
# 統計情報を取得
stats = requests.get('http://localhost:8081/stats').json()
print(f"Camera: {stats['camera']}, Detections: {stats['detections']}")
# 検出結果を削除
delete_response = requests.delete(
'http://localhost:8080/detection/camera1/2026-02-02 06:55:33'
)
print(delete_response.json())
JavaScript(ブラウザ)
// 検出一覧を取得して表示
async function loadDetections() {
const response = await fetch('/detections');
const data = await response.json();
console.log(`Total: ${data.total}`);
data.recent.forEach(detection => {
console.log(`${detection.time} - ${detection.camera} (${detection.confidence})`);
});
}
// 統計情報を定期取得
setInterval(async () => {
const stats = await fetch('http://localhost:8081/stats').then(r => r.json());
document.getElementById('detections').textContent = stats.detections;
document.getElementById('status').textContent = stats.stream_alive ? 'Online' : 'Offline';
}, 2000);
// 検出結果を削除
async function deleteDetection(camera, timestamp) {
const response = await fetch(`/detection/${camera}/${timestamp}`, {
method: 'DELETE'
});
const result = await response.json();
if (result.success) {
alert(result.message);
loadDetections(); // リストを更新
} else {
alert(`Error: ${result.error}`);
}
}
// 非流星検出を一括削除
async function bulkDeleteNonMeteor(camera) {
const response = await fetch(`/bulk_delete_non_meteor/${camera}`, {
method: 'POST'
});
const result = await response.json();
if (result.success) {
alert(`${result.deleted_count}件の非流星検出を削除しました`);
loadDetections(); // リストを更新
} else {
alert(`Error: ${result.error}`);
}
}
// 検出にラベルを設定
async function setDetectionLabel(camera, timestamp, label) {
const response = await fetch('/detection_label', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({camera, timestamp, label})
});
const result = await response.json();
if (result.success) {
alert('ラベル設定完了');
loadDetections(); // リストを更新
} else {
alert(`Error: ${result.error}`);
}
}
Node.js
const axios = require('axios');
// 全カメラの統計を取得
async function getAllStats() {
const cameras = [8081, 8082, 8083];
const promises = cameras.map(port =>
axios.get(`http://localhost:${port}/stats`)
);
const results = await Promise.all(promises);
results.forEach((res, i) => {
console.log(`Camera ${i+1}:`, res.data.detections, 'detections');
});
}
getAllStats();
Bash
#!/bin/bash
# 全カメラの統計を表示
echo "=== Meteor Detection Stats ==="
for port in 8081 8082 8083; do
stats=$(curl -s "http://localhost:$port/stats")
camera=$(echo "$stats" | jq -r '.camera')
detections=$(echo "$stats" | jq -r '.detections')
alive=$(echo "$stats" | jq -r '.stream_alive')
echo "$camera: $detections detections (stream: $alive)"
done
# 検出一覧を取得
echo ""
echo "=== Recent Detections ==="
curl -s "http://localhost:8080/detections" | \
jq -r '.recent[] | "\(.time) - \(.camera) (\(.confidence))"'
PowerShell
# 検出一覧を取得
$detections = Invoke-RestMethod -Uri "http://localhost:8080/detections"
Write-Host "Total detections: $($detections.total)"
# 統計情報を取得
$stats = Invoke-RestMethod -Uri "http://localhost:8081/stats"
Write-Host "Camera: $($stats.camera), Detections: $($stats.detections)"
# 検出結果を削除
$deleteResult = Invoke-RestMethod `
-Uri "http://localhost:8080/detection/camera1/2026-02-02%2006:55:33" `
-Method Delete
Write-Host $deleteResult.message
Webhook連携例
検出時にSlackに通知
# webhook_notifier.py
import requests
import time
import json
SLACK_WEBHOOK_URL = "https://hooks.slack.com/services/YOUR/WEBHOOK/URL"
last_count = {}
def check_detections():
for port in [8081, 8082, 8083]:
stats = requests.get(f'http://localhost:{port}/stats').json()
camera = stats['camera']
count = stats['detections']
if camera not in last_count:
last_count[camera] = count
if count > last_count[camera]:
# 新しい検出があった
message = {
"text": f"🌠 流星検出!\nカメラ: {camera}\n検出数: {count}"
}
requests.post(SLACK_WEBHOOK_URL, json=message)
last_count[camera] = count
# 10秒ごとに確認
while True:
check_detections()
time.sleep(10)
検出時にメール送信
# email_notifier.py
import requests
import smtplib
from email.mime.text import MIMEText
def send_email(subject, body):
msg = MIMEText(body)
msg['Subject'] = subject
msg['From'] = 'meteor@example.com'
msg['To'] = 'admin@example.com'
with smtplib.SMTP('smtp.example.com', 587) as server:
server.starttls()
server.login('user', 'password')
server.send_message(msg)
def monitor():
last_count = {}
while True:
detections = requests.get('http://localhost:8080/detections').json()
for detection in detections['recent']:
key = f"{detection['camera']}_{detection['time']}"
if key not in last_count:
send_email(
f"流星検出: {detection['camera']}",
f"時刻: {detection['time']}\n信頼度: {detection['confidence']}"
)
last_count[key] = True
time.sleep(30)
monitor()
APIクライアントライブラリ例
Python用シンプルクライアント
# meteor_client.py
import requests
from typing import List, Dict, Optional
class MeteorDetectionClient:
def __init__(self, dashboard_url: str = "http://localhost:8080"):
self.dashboard_url = dashboard_url
def get_detections(self) -> Dict:
"""検出一覧を取得"""
response = requests.get(f"{self.dashboard_url}/detections")
return response.json()
def get_detection_window(self, lat: float = None, lon: float = None) -> Dict:
"""検出時間帯を取得"""
params = {}
if lat: params['lat'] = lat
if lon: params['lon'] = lon
response = requests.get(
f"{self.dashboard_url}/detection_window",
params=params
)
return response.json()
def delete_detection(self, camera: str, timestamp: str) -> Dict:
"""検出結果を削除"""
response = requests.delete(
f"{self.dashboard_url}/detection/{camera}/{timestamp}"
)
return response.json()
def bulk_delete_non_meteor(self, camera: str) -> Dict:
"""非流星検出を一括削除"""
response = requests.post(
f"{self.dashboard_url}/bulk_delete_non_meteor/{camera}"
)
return response.json()
def set_detection_label(self, camera: str, timestamp: str, label: str) -> Dict:
"""検出にラベルを設定"""
response = requests.post(
f"{self.dashboard_url}/detection_label",
json={"camera": camera, "timestamp": timestamp, "label": label}
)
return response.json()
def get_camera_stats(self, port: int) -> Dict:
"""カメラの統計情報を取得"""
response = requests.get(f"http://localhost:{port}/stats")
return response.json()
def get_camera_stats_from_dashboard(self, index: int) -> Dict:
"""ダッシュボード経由でカメラ統計を取得(監視情報含む)"""
response = requests.get(f"{self.dashboard_url}/camera_stats/{index}")
return response.json()
# 使用例
if __name__ == "__main__":
client = MeteorDetectionClient()
# 検出一覧を取得
detections = client.get_detections()
print(f"Total: {detections['total']}")
# カメラ統計を取得
stats = client.get_camera_stats(8081)
print(f"Camera: {stats['camera']}, Detections: {stats['detections']}")
# ダッシュボード経由でカメラ統計取得(監視情報含む)
dashboard_stats = client.get_camera_stats_from_dashboard(0)
print(f"Monitor enabled: {dashboard_stats['monitor_enabled']}")
print(f"Restart count: {dashboard_stats['monitor_restart_count']}")
# 検出にラベルを設定
result = client.set_detection_label(
camera="camera1",
timestamp="2026-02-02 06:55:33",
label="meteor"
)
print(result)
# 非流星検出を一括削除
delete_result = client.bulk_delete_non_meteor("camera1")
print(f"Deleted {delete_result['deleted_count']} non-meteor detections")
関連ドキュメント
- OPERATIONS_GUIDE.md - 運用ガイド
- ARCHITECTURE.md - システムアーキテクチャ
- DETECTOR_COMPONENTS.md - 検出コンポーネント詳細