# Momentry JSON 輸出檔案規範 | 項目 | 內容 | |------|------| | 建立者 | Warren | | 建立時間 | 2026-03-16 | | 文件版本 | V1.0 | --- ## 版本歷史 | 版本 | 日期 | 目的 | 操作人 | 工具/模型 | |------|------|------|--------|-----------| | V1.0 | 2026-03-16 | 創建文件 | Warren | OpenCode / MiniMax M2.5 | --- 本文檔定義 Momentry Core 系統中所有 JSON 輸出檔案的結構、命名規範與儲存位置。 --- ## 1. 輸出檔案總覽 ### 1.1 檔案類型 | 類型 | 前綴 | 說明 | 狀態 | |------|------|------|------| | **Probe** | `{uuid}.probe.json` | 影片元數據 | ✅ 已實作 | | **ASR** | `{uuid}.asr.json` | 語音識別結果 | ✅ 已實作 | | **ASRx** | `{uuid}.asrx.json` | 說話者分離 | 🔜 規劃中 | | **OCR** | `{uuid}.ocr.json` | 文字辨識結果 | 🔜 規劃中 | | **YOLO** | `{uuid}.yolo.json` | 物件偵測結果 | 🔜 規劃中 | | **Face** | `{uuid}.face.json` | 人臉偵測結果 | 🔜 規劃中 | | **Pose** | `{uuid}.pose.json` | 姿態估計結果 | 🔜 規劃中 | | **Thumbnail** | `{uuid}/thumb_XXX.jpg` | 縮圖檔案 | ✅ 已實作 | ### 1.2 命名規範 ``` {UUID}.{類型}.json 範例: 1636719dc31f78ac.probe.json - 影片探測結果 1636719dc31f78ac.asr.json - 語音識別結果 1636719dc31f78ac.ocr.json - 文字辨識結果 ``` - **UUID**: 16 字元,基於檔案路徑計算 - **類型**: 小寫 snake_case - **副檔名**: `.json` --- ## 2. 輸出目錄結構 ### 2.1 預設輸出位置 ``` momentry_core_0.1/ ├── {uuid}.probe.json # 影片探測 ├── {uuid}.asr.json # 語音識別 ├── {uuid}.asrx.json # 說話者分離 ├── {uuid}.ocr.json # 文字辨識 ├── {uuid}.yolo.json # 物件偵測 ├── {uuid}.face.json # 人臉偵測 ├── {uuid}.pose.json # 姿態估計 └── thumbnails/ └── {uuid}/ ├── thumb_000.jpg ├── thumb_001.jpg └── ... ``` ### 2.2 儲存策略 | 資料類型 | 儲存位置 | 說明 | |----------|----------|------| | JSON 檔案 | 專案根目錄 | 方便快速存取 | | 縮圖 | thumbnails/{uuid}/ | 分離儲存 | | 資料庫 | PostgreSQL | 長期儲存 | --- ## 3. JSON 結構定義 ### 3.1 Probe (影片探測) **檔案**: `{uuid}.probe.json` ```json { "streams": [ { "index": 0, "codec_name": "h264", "codec_type": "video", "width": 1920, "height": 1080, "r_frame_rate": "60000/1001", "duration": "6879.329524", "sample_rate": null, "channels": null }, { "index": 1, "codec_name": "aac", "codec_type": "audio", "width": null, "height": null, "r_frame_rate": "0/0", "duration": "6879.245333", "sample_rate": "48000", "channels": 2 } ], "format": { "filename": "/path/to/video.mov", "format_name": "mov,mp4,m4a,3gp,3g2,mj2", "duration": "6879.329524", "size": "2361629896", "bit_rate": "2748000" } } ``` **欄位說明**: | 欄位 | 類型 | 說明 | |------|------|------| | `streams` | Array | 媒體串流陣列 | | `streams[].index` | Integer | 串流索引 | | `streams[].codec_name` | String | 編碼名稱 | | `streams[].codec_type` | String | 串流類型 (video/audio) | | `streams[].width` | Integer | 寬度 (video) | | `streams[].height` | Integer | 高度 (video) | | `streams[].r_frame_rate` | String | 幀率 | | `streams[].duration` | String | 持續時間 (秒) | | `streams[].sample_rate` | String | 採樣率 (audio) | | `streams[].channels` | Integer | 聲道數 (audio) | | `format` | Object | 檔案格式資訊 | | `format.filename` | String | 原始檔案路徑 | | `format.format_name` | String | 格式名稱 | | `format.duration` | String | 總時長 (秒) | | `format.size` | String | 檔案大小 (bytes) | | `format.bit_rate` | String | 位元率 | --- ### 3.2 ASR (語音識別) **檔案**: `{uuid}.asr.json` ```json { "language": "en", "language_probability": 0.9945855736732483, "segments": [ { "start": 0.0, "end": 19.04, "text": "Hello and welcome to the old-time movie show." }, { "start": 19.04, "end": 25.44, "text": "Today we are featuring the 1963 comedy mystery film Charade." } ] } ``` **欄位說明**: | 欄位 | 類型 | 說明 | |------|------|------| | `language` | String | 偵測語言代碼 (ISO 639-1) | | `language_probability` | Float | 語言偵測機率 (0-1) | | `segments` | Array | 語音分段陣列 | | `segments[].start` | Float | 開始時間 (秒) | | `segments[].end` | Float | 結束時間 (秒) | | `segments[].text` | String | 識別文字 | --- ### 3.3 ASRx (說話者分離) **檔案**: `{uuid}.asrx.json` ```json { "language": "en", "language_probability": 0.95, "segments": [ { "start": 0.0, "end": 19.04, "text": "Hello and welcome to the old-time movie show.", "speaker_id": "SPEAKER_00", "speaker_embedding": [0.123, -0.456, ...] }, { "start": 19.04, "end": 25.44, "text": "Today we are featuring the 1963 comedy mystery film Charade.", "speaker_id": "SPEAKER_01", "speaker_embedding": [0.789, -0.123, ...] } ] } ``` **欄位說明**: | 欄位 | 類型 | 說明 | |------|------|------| | `language` | String | 偵測語言代碼 | | `language_probability` | Float | 語言偵測機率 | | `segments` | Array | 語音分段陣列 | | `segments[].start` | Float | 開始時間 (秒) | | `segments[].end` | Float | 結束時間 (秒) | | `segments[].text` | String | 識別文字 | | `segments[].speaker_id` | String | 說話者 ID | | `segments[].speaker_embedding` | Array | 說話者嵌入向量 (可選) | --- ### 3.4 OCR (文字辨識) **檔案**: `{uuid}.ocr.json` ```json { "segments": [ { "start": 10.5, "end": 12.3, "text": "EXAMPLE TEXT", "boxes": [ { "x1": 100, "y1": 50, "x2": 400, "y2": 100 } ], "confidence": 0.95 } ] } ``` **欄位說明**: | 欄位 | 類型 | 說明 | |------|------|------| | `segments` | Array | OCR 分段陣列 | | `segments[].start` | Float | 開始時間 (秒) | | `segments[].end` | Float | 結束時間 (秒) | | `segments[].text` | String | 辨識文字 | | `segments[].boxes` | Array | 文字邊界框陣列 | | `segments[].boxes[].x1` | Integer | 左上 X 座標 | | `segments[].boxes[].y1` | Integer | 左上 Y 座標 | | `segments[].boxes[].x2` | Integer | 右下 X 座標 | | `segments[].boxes[].y2` | Integer | 右下 Y 座標 | | `segments[].confidence` | Float | 辨識信心度 | --- ### 3.5 YOLO (物件偵測) **檔案**: `{uuid}.yolo.json` ```json { "segments": [ { "start": 0.0, "end": 1.0, "objects": [ { "class": "person", "confidence": 0.92, "box": { "x1": 150, "y1": 200, "x2": 400, "y2": 800 } }, { "class": "car", "confidence": 0.87, "box": { "x1": 800, "y1": 400, "x2": 1200, "y2": 700 } } ] } ] } ``` **欄位說明**: | 欄位 | 類型 | 說明 | |------|------|------| | `segments` | Array | 時間分段陣列 | | `segments[].start` | Float | 開始時間 (秒) | | `segments[].end` | Float | 結束時間 (秒) | | `segments[].objects` | Array | 偵測物件陣列 | | `segments[].objects[].class` | String | 物件類別 | | `segments[].objects[].confidence` | Float | 偵測信心度 | | `segments[].objects[].box` | Object | 邊界框 | --- ### 3.6 Face (人臉偵測) **檔案**: `{uuid}.face.json` ```json { "segments": [ { "start": 0.0, "end": 1.0, "faces": [ { "face_id": "face_001", "box": { "x1": 100, "y1": 50, "x2": 300, "y2": 350 }, "embedding": [0.123, -0.456, ...], "emotion": "happy", "age": 35, "gender": "female" } ] } ] } ``` **欄位說明**: | 欄位 | 類型 | 說明 | |------|------|------| | `segments` | Array | 時間分段陣列 | | `segments[].start` | Float | 開始時間 (秒) | | `segments[].end` | Float | 結束時間 (秒) | | `segments[].faces` | Array | 人臉陣列 | | `segments[].faces[].face_id` | String | 人臉 ID | | `segments[].faces[].box` | Object | 邊界框 | | `segments[].faces[].embedding` | Array | 人臉嵌入向量 | | `segments[].faces[].emotion` | String | 情緒分類 (可選) | | `segments[].faces[].age` | Integer | 年齡估計 (可選) | | `segments[].faces[].gender` | String | 性別估計 (可選) | --- ### 3.7 Pose (姿態估計) **檔案**: `{uuid}.pose.json` ```json { "segments": [ { "start": 0.0, "end": 1.0, "poses": [ { "person_id": "person_001", "keypoints": { "nose": {"x": 320, "y": 120, "confidence": 0.98}, "left_eye": {"x": 335, "y": 110, "confidence": 0.95}, "right_eye": {"x": 305, "y": 110, "confidence": 0.93}, "left_shoulder": {"x": 280, "y": 180, "confidence": 0.91}, "right_shoulder": {"x": 360, "y": 180, "confidence": 0.89} }, "confidence": 0.92 } ] } ] } ``` **欄位說明**: | 欄位 | 類型 | 說明 | |------|------|------| | `segments` | Array | 時間分段陣列 | | `segments[].start` | Float | 開始時間 (秒) | | `segments[].end` | Float | 結束時間 (秒) | | `segments[].poses` | Array | 姿態陣列 | | `segments[].poses[].person_id` | String | 人員 ID | | `segments[].poses[].keypoints` | Object | 關鍵點 | | `segments[].poses[].keypoints.{name}` | Object | 各關鍵點 | | `segments[].poses[].keypoints.{name}.x` | Integer | X 座標 | | `segments[].poses[].keypoints.{name}.y` | Integer | Y 座標 | | `segments[].poses[].keypoints.{name}.confidence` | Float | 信心度 | | `segments[].poses[].confidence` | Float | 整體信心度 | --- ## 4. 處理流程 ### 4.1 處理管線 ``` 影片檔案 │ ▼ ┌─────────────────┐ │ 1. Register │ 建立 UUID,註冊影片 └────────┬────────┘ │ ┌────▼────┐ │ 2. Probe │ ffprobe 擷取元數據 └────┬────┘ │ {uuid}.probe.json ┌────▼─────┐ │ 3. ASR │ faster-whisper 語音識別 └────┬─────┘ │ {uuid}.asr.json ┌────▼──────┐ │ 4. ASRx │ 說話者分離 (pyannote) └────┬──────┘ │ {uuid}.asrx.json ┌────▼────┐ │ 5. OCR │ Tesseract 文字辨識 └────┬────┘ │ {uuid}.ocr.json ┌────▼────┐ │ 6. YOLO │ 物件偵測 └────┬────┘ │ {uuid}.yolo.json ┌────▼────┐ │ 7. Face │ 人臉偵測 └────┬────┘ │ {uuid}.face.json ┌────▼────┐ │ 8. Pose │ 姿態估計 └────┬────┘ │ {uuid}.pose.json ┌────▼──────┐ │ 9. Chunk │ 轉換為資料庫 chunks └───────────┘ ``` ### 4.2 失敗處理 | 階段 | 失敗時 | 處理 | |------|--------|------| | Probe | 無法讀取影片 | 終止流程,輸出錯誤 | | ASR | 無音軌 | 產生空 segments,繼續流程 | | OCR/YOLO/Face/Pose | 處理失敗 | 跳過該階段,記錄日誌 | --- ## 5. 資料庫儲存 ### 5.1 Chunk 結構 ```sql CREATE TABLE chunks ( id BIGSERIAL PRIMARY KEY, uuid VARCHAR(16) NOT NULL, chunk_id VARCHAR(64) NOT NULL, chunk_index INTEGER NOT NULL, chunk_type VARCHAR(32) NOT NULL, start_time DOUBLE PRECISION NOT NULL, end_time DOUBLE PRECISION NOT NULL, content JSONB NOT NULL, created_at TIMESTAMP DEFAULT NOW(), UNIQUE(uuid, chunk_id) ); ``` ### 5.2 轉換範例 ```rust // ASR → Chunk (Sentence) for (i, seg) in asr_result.segments.iter().enumerate() { let chunk = Chunk::new( uuid.clone(), i as u32, ChunkType::Sentence, seg.start, seg.end, serde_json::json!({"text": seg.text}), ); db.store_chunk(&chunk).await?; } ``` --- ## 6. 版本歷史 | 版本 | 日期 | 變更 | |------|------|------| | 1.0.0 | 2026-03-16 | 初始版本 | --- ## 7. 相關文件 - [RUST_DEVELOPMENT.md](./RUST_DEVELOPMENT.md) - Rust 開發規範 - [AGENTS.md](../AGENTS.md) - 開發規範 - [monitor_config.yaml](../monitor/config/monitor_config.yaml) - 監控配置