docs: Add JSON output specification
- Define all JSON output file types and naming conventions - Document Probe, ASR, ASRx, OCR, YOLO, Face, Pose structures - Include field descriptions and data types - Add processing pipeline flow - Document database storage schema
This commit is contained in:
503
docs/JSON_OUTPUT_SPEC.md
Normal file
503
docs/JSON_OUTPUT_SPEC.md
Normal file
@@ -0,0 +1,503 @@
|
|||||||
|
# Momentry JSON 輸出檔案規範
|
||||||
|
|
||||||
|
本文檔定義 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) - 監控配置
|
||||||
Reference in New Issue
Block a user