---
document_type: "reference_doc"
service: "MOMENTRY_CORE"
title: "Momentry JSON 輸出檔案規範"
date: "2026-03-16"
version: "V1.0"
status: "active"
owner: "Warren"
created_by: "OpenCode"
tags:
  - "momentry"
  - "json"
  - "輸出檔案規範"
ai_query_hints:
  - "查詢 Momentry JSON 輸出檔案規範 的內容"
  - "Momentry JSON 輸出檔案規範 的主要目的是什麼？"
  - "如何操作或實施 Momentry JSON 輸出檔案規範？"
---

# 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) - 監控配置
