Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c5ba252d89 | |||
| e4d3365ada | |||
| de80b96c15 | |||
| f5436c7b63 | |||
| 845704724f |
@@ -0,0 +1,267 @@
|
|||||||
|
# Momentry Core API 教育訓練手冊
|
||||||
|
|
||||||
|
> **對象**: marcom 團隊
|
||||||
|
> **版本**: V1.1 | **日期**: 2026-03-25
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 快速開始
|
||||||
|
|
||||||
|
### 基本資訊
|
||||||
|
|
||||||
|
| 項目 | 值 |
|
||||||
|
|------|-----|
|
||||||
|
| API 網址 | `https://api.momentry.ddns.net` |
|
||||||
|
| 認證方式 | Header `X-API-Key` |
|
||||||
|
| 格式 | JSON |
|
||||||
|
|
||||||
|
### API Key
|
||||||
|
|
||||||
|
```
|
||||||
|
X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 快速範例
|
||||||
|
|
||||||
|
### 查詢所有影片
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -s -H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69" \
|
||||||
|
"https://api.momentry.ddns.net/api/v1/videos"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 查詢單一影片
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -s -H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69" \
|
||||||
|
"https://api.momentry.ddns.net/api/v1/videos/{uuid}"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 查詢處理任務狀態
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -s -H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69" \
|
||||||
|
"https://api.momentry.ddns.net/api/v1/jobs/{uuid}"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. API 端點說明
|
||||||
|
|
||||||
|
### 3.1 影片相關
|
||||||
|
|
||||||
|
#### GET /api/v1/videos
|
||||||
|
取得所有影片列表
|
||||||
|
|
||||||
|
**回應範例**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"videos": [
|
||||||
|
{
|
||||||
|
"uuid": "5dea6618a606e7c7",
|
||||||
|
"filename": "demo_video.mp4",
|
||||||
|
"duration": 123.45,
|
||||||
|
"status": "ready",
|
||||||
|
"created_at": "2026-03-25T10:00:00Z"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### GET /api/v1/videos/:uuid
|
||||||
|
取得單一影片詳情
|
||||||
|
|
||||||
|
### 3.2 任務相關
|
||||||
|
|
||||||
|
#### GET /api/v1/jobs/:uuid
|
||||||
|
查詢處理任務狀態
|
||||||
|
|
||||||
|
**回應範例**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"uuid": "9760d0820f0cf9a7",
|
||||||
|
"video_uuid": "5dea6618a606e7c7",
|
||||||
|
"status": "completed",
|
||||||
|
"progress": 100,
|
||||||
|
"created_at": "2026-03-25T10:00:00Z",
|
||||||
|
"completed_at": "2026-03-25T10:05:00Z"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### GET /api/v1/jobs
|
||||||
|
查詢所有任務
|
||||||
|
|
||||||
|
**查詢參數**:
|
||||||
|
| 參數 | 說明 | 範例 |
|
||||||
|
|------|------|------|
|
||||||
|
| `status` | 篩選狀態 | `pending`, `processing`, `completed`, `failed` |
|
||||||
|
| `limit` | 回傳數量 | `10` |
|
||||||
|
|
||||||
|
**範例**:
|
||||||
|
```bash
|
||||||
|
curl -s -H "X-API-Key: ..." \
|
||||||
|
"https://api.momentry.ddns.net/api/v1/jobs?status=completed&limit=5"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 系統管理
|
||||||
|
|
||||||
|
#### POST /api/v1/config/cache
|
||||||
|
切換快取功能(管理員專用)
|
||||||
|
|
||||||
|
**請求範例**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"enabled": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**回應範例**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"cache_enabled": true,
|
||||||
|
"message": "Cache toggled successfully"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### POST /api/v1/unregister
|
||||||
|
刪除影片及其所有關聯資料(管理員專用)
|
||||||
|
|
||||||
|
**請求範例**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"uuid": "5dea6618a606e7c7"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**回應範例**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": "Video unregistered successfully",
|
||||||
|
"uuid": "5dea6618a606e7c7"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**注意**: 此操作會刪除影片及其所有分段、處理結果、縮圖等關聯資料,**無法復原**。
|
||||||
|
|
||||||
|
### 3.4 健康檢查
|
||||||
|
|
||||||
|
#### GET /health
|
||||||
|
服務健康狀態(**無需認證**)
|
||||||
|
|
||||||
|
**回應**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "ok",
|
||||||
|
"version": "0.9.20260325_144654"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. n8n Workflow 範例
|
||||||
|
|
||||||
|
### 4.1 基本設定
|
||||||
|
|
||||||
|
在 n8n workflow 中使用 HTTP Request 節點:
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────┐
|
||||||
|
│ HTTP Request │
|
||||||
|
├─────────────────┤
|
||||||
|
│ Method: GET │
|
||||||
|
│ URL: https://api.momentry.ddns.net/api/v1/videos
|
||||||
|
│ Headers: │
|
||||||
|
│ X-API-Key: │
|
||||||
|
│ [YOUR_KEY] │
|
||||||
|
└────────┬────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────┐
|
||||||
|
│ 處理回應資料 │
|
||||||
|
└─────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 範例:檢查任務狀態
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// n8n Function Node 範例
|
||||||
|
const jobUuid = $input.item.json.uuid;
|
||||||
|
|
||||||
|
return [{
|
||||||
|
json: {
|
||||||
|
method: "GET",
|
||||||
|
url: `https://api.momentry.ddns.net/api/v1/jobs/${jobUuid}`,
|
||||||
|
headers: {
|
||||||
|
"X-API-Key": "YOUR_API_KEY"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 常見問題
|
||||||
|
|
||||||
|
### Q: 返回 401 錯誤怎麼辦?
|
||||||
|
確認 Header 中有正確的 `X-API-Key` 值
|
||||||
|
|
||||||
|
### Q: 如何確認影片處理完成?
|
||||||
|
```
|
||||||
|
GET /api/v1/jobs/{uuid}
|
||||||
|
```
|
||||||
|
檢查 `status` 是否為 `completed`
|
||||||
|
|
||||||
|
### Q: 查不到資料?
|
||||||
|
確認 UUID 格式正確(16碼 hex 字串)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 快速參考卡
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ Momentry API 速查 │
|
||||||
|
├─────────────────────────────────────────────────────────────┤
|
||||||
|
│ 查詢所有影片 GET /api/v1/videos │
|
||||||
|
│ 查詢單一影片 GET /api/v1/videos/:uuid │
|
||||||
|
│ 查詢任務狀態 GET /api/v1/jobs/:uuid │
|
||||||
|
│ 查詢所有任務 GET /api/v1/jobs │
|
||||||
|
│ 快取設定 POST /api/v1/config/cache (管理員) │
|
||||||
|
│ 刪除影片 POST /api/v1/unregister (管理員) │
|
||||||
|
│ 健康檢查 GET /health (免認證) │
|
||||||
|
├─────────────────────────────────────────────────────────────┤
|
||||||
|
│ Header: X-API-Key: [YOUR_KEY] │
|
||||||
|
│ URL: https://api.momentry.ddns.net │
|
||||||
|
└─────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 附錄:回應狀態說明
|
||||||
|
|
||||||
|
### 任務狀態 (status)
|
||||||
|
|
||||||
|
| 狀態 | 說明 |
|
||||||
|
|------|------|
|
||||||
|
| `pending` | 等待處理 |
|
||||||
|
| `processing` | 處理中 |
|
||||||
|
| `completed` | 已完成 |
|
||||||
|
| `failed` | 處理失敗 |
|
||||||
|
|
||||||
|
### 影片狀態 (status)
|
||||||
|
|
||||||
|
| 狀態 | 說明 |
|
||||||
|
|------|------|
|
||||||
|
| `uploading` | 上傳中 |
|
||||||
|
| `pending` | 等待處理 |
|
||||||
|
| `processing` | 處理中 |
|
||||||
|
| `ready` | 已就緒 |
|
||||||
|
| `error` | 錯誤 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**文件版本**: V1.1
|
||||||
|
**最後更新**: 2026-03-25
|
||||||
@@ -0,0 +1,391 @@
|
|||||||
|
# Momentry API 使用流程
|
||||||
|
|
||||||
|
> **目標**: 從影片上傳到搜尋的完整流程
|
||||||
|
> **適用**: WordPress / n8n 整合
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 流程總覽
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
|
||||||
|
│ 1. 上傳 │ → │ 2. 註冊 │ → │ 3. 確認 │ → │ 4. 處理 │ → │ 5. 搜尋 │
|
||||||
|
│ SFTPGo │ │ 自動完成 │ │ UUID │ │ 查詢進度 │ │ 測試 │
|
||||||
|
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 1: 上傳影片
|
||||||
|
|
||||||
|
### 方式 A: SFTP 上傳(推薦)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 連線資訊
|
||||||
|
主機: momentry.ddns.net
|
||||||
|
連接埠: 2022
|
||||||
|
用戶名: demo
|
||||||
|
密碼: demopassword123
|
||||||
|
```
|
||||||
|
|
||||||
|
使用 FileZilla 或 SFTP 客戶端上傳到 `/` 目錄
|
||||||
|
|
||||||
|
### 方式 B: SFTP 命令列
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sshpass -p "demopassword123" sftp -P 2022 demo@momentry.ddns.net
|
||||||
|
```
|
||||||
|
|
||||||
|
上傳後確認檔案在 SFTPGo 中的位置
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 2: 自動註冊
|
||||||
|
|
||||||
|
上傳後,系統會自動:
|
||||||
|
1. 偵測新檔案
|
||||||
|
2. 計算 UUID(SHA256)
|
||||||
|
3. 建立資料庫記錄
|
||||||
|
|
||||||
|
**無需手動操作**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 3: 確認註冊成功
|
||||||
|
|
||||||
|
### 查詢所有影片
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -s -H "X-API-Key: YOUR_API_KEY" \
|
||||||
|
"https://api.momentry.ddns.net/api/v1/videos" | jq '.videos | length'
|
||||||
|
```
|
||||||
|
|
||||||
|
### 查詢特定檔案
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -s -H "X-API-Key: YOUR_API_KEY" \
|
||||||
|
"https://api.momentry.ddns.net/api/v1/videos" | jq '.videos[] | select(.file_name | contains("你的檔案名"))'
|
||||||
|
```
|
||||||
|
|
||||||
|
### 預期回應
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"uuid": "952f5854b9febad1",
|
||||||
|
"file_path": "/Users/accusys/momentry/var/sftpgo/data/demo/你的檔案.mp4",
|
||||||
|
"file_name": "你的檔案.mp4",
|
||||||
|
"duration": 123.45,
|
||||||
|
"width": 1920,
|
||||||
|
"height": 1080
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**確認要點**:
|
||||||
|
- ✅ UUID 已產生(16位 hex)
|
||||||
|
- ✅ `file_path` 正確
|
||||||
|
- ✅ `duration` > 0
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 4: 查詢處理進度
|
||||||
|
|
||||||
|
### 取得任務 UUID
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 從影片資訊取得 job_id
|
||||||
|
curl -s -H "X-API-Key: YOUR_API_KEY" \
|
||||||
|
"https://api.momentry.ddns.net/api/v1/videos" | \
|
||||||
|
jq '.videos[] | select(.file_name == "你的檔案.mp4") | {uuid, job_id}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### 查詢任務狀態
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -s -H "X-API-Key: YOUR_API_KEY" \
|
||||||
|
"https://api.momentry.ddns.net/api/v1/jobs/{uuid}"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 任務狀態說明
|
||||||
|
|
||||||
|
| status | 說明 | 動作 |
|
||||||
|
|--------|------|------|
|
||||||
|
| `pending` | 等待處理 | 等待中 |
|
||||||
|
| `processing` | 處理中 | 繼續輪詢 |
|
||||||
|
| `completed` | 已完成 | 可進入 Step 5 |
|
||||||
|
| `failed` | 處理失敗 | 檢查錯誤 |
|
||||||
|
|
||||||
|
### n8n 輪詢範例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// n8n Workflow: 檢查處理狀態
|
||||||
|
const jobUuid = $input.item.json.job_uuid;
|
||||||
|
|
||||||
|
const response = await fetch(
|
||||||
|
`https://api.momentry.ddns.net/api/v1/jobs/${jobUuid}`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
"X-API-Key": "YOUR_API_KEY"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const job = await response.json();
|
||||||
|
|
||||||
|
// 狀態檢查
|
||||||
|
if (job.status === 'completed') {
|
||||||
|
return [{ json: { done: true, video_uuid: job.video_uuid } }];
|
||||||
|
} else {
|
||||||
|
return [{ json: { done: false, status: job.status } }];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 5: 搜尋測試
|
||||||
|
|
||||||
|
處理完成後,資料會入庫到向量資料庫,可進行搜尋測試。
|
||||||
|
|
||||||
|
### 測試向量搜尋
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -s -X POST "https://api.momentry.ddns.net/api/v1/search" \
|
||||||
|
-H "X-API-Key: YOUR_API_KEY" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"query": "測試關鍵字",
|
||||||
|
"top_k": 5
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 完整 n8n Workflow 範例
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────┐
|
||||||
|
│ 觸發 (定時) │
|
||||||
|
└──────┬───────┘
|
||||||
|
▼
|
||||||
|
┌──────────────┐ ┌──────────────┐
|
||||||
|
│ 查詢影片 │────►│ 比對新檔案 │
|
||||||
|
│ /videos │ │ │
|
||||||
|
└──────┬───────┘ └──────────────┘
|
||||||
|
│ │
|
||||||
|
▼ ▼
|
||||||
|
┌──────────────┐ ┌──────────────┐
|
||||||
|
│ 等待處理 │◄────│ 輪詢任務狀態 │
|
||||||
|
│ /jobs/:uuid │ │ /jobs/:uuid │
|
||||||
|
└──────┬───────┘ └──────────────┘
|
||||||
|
│
|
||||||
|
▼ (completed)
|
||||||
|
┌──────────────┐
|
||||||
|
│ 搜尋測試 │
|
||||||
|
│ /search │
|
||||||
|
└──────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 快速參考
|
||||||
|
|
||||||
|
| 步驟 | API | 用途 |
|
||||||
|
|------|-----|------|
|
||||||
|
| 查詢影片 | `GET /api/v1/videos` | 確認上傳成功 |
|
||||||
|
| 查詢任務 | `GET /api/v1/jobs/:uuid` | 查看處理進度 |
|
||||||
|
| 搜尋內容 | `POST /api/v1/search` | 測試搜尋功能 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## WordPress PHP 範例
|
||||||
|
|
||||||
|
### 基本設定
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
class Momentry_API {
|
||||||
|
private const API_URL = 'https://api.momentry.ddns.net';
|
||||||
|
private const API_KEY = 'YOUR_API_KEY';
|
||||||
|
|
||||||
|
public static function request(string $method, string $endpoint, ?array $data = null): array {
|
||||||
|
$url = self::API_URL . $endpoint;
|
||||||
|
|
||||||
|
$args = [
|
||||||
|
'method' => $method,
|
||||||
|
'headers' => [
|
||||||
|
'X-API-Key' => self::API_KEY,
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
],
|
||||||
|
'timeout' => 30,
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($data !== null) {
|
||||||
|
$args['body'] = json_encode($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = wp_remote_request($url, $args);
|
||||||
|
|
||||||
|
if (is_wp_error($response)) {
|
||||||
|
throw new Exception($response->get_error_message());
|
||||||
|
}
|
||||||
|
|
||||||
|
return json_decode(wp_remote_retrieve_body($response), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getVideos(): array {
|
||||||
|
return self::request('GET', '/api/v1/videos');
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getVideo(string $uuid): array {
|
||||||
|
return self::request('GET', "/api/v1/videos/{$uuid}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getJob(string $uuid): array {
|
||||||
|
return self::request('GET', "/api/v1/jobs/{$uuid}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function search(string $query, int $topK = 5): array {
|
||||||
|
return self::request('POST', '/api/v1/search', [
|
||||||
|
'query' => $query,
|
||||||
|
'top_k' => $topK,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3: 確認註冊成功
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
// 查詢所有影片
|
||||||
|
$videos = Momentry_API::getVideos();
|
||||||
|
|
||||||
|
foreach ($videos['videos'] as $video) {
|
||||||
|
echo "UUID: " . $video['uuid'] . "\n";
|
||||||
|
echo "檔案: " . $video['file_name'] . "\n";
|
||||||
|
echo "時長: " . $video['duration'] . " 秒\n";
|
||||||
|
echo "---\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查詢特定影片
|
||||||
|
$video = Momentry_API::getVideo('952f5854b9febad1');
|
||||||
|
print_r($video);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 4: 查詢處理進度
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
// 取得任務狀態
|
||||||
|
$job = Momentry_API::getJob('9760d0820f0cf9a7');
|
||||||
|
|
||||||
|
switch ($job['status']) {
|
||||||
|
case 'pending':
|
||||||
|
echo "等待處理中...\n";
|
||||||
|
break;
|
||||||
|
case 'processing':
|
||||||
|
echo "處理中: " . $job['progress'] . "%\n";
|
||||||
|
break;
|
||||||
|
case 'completed':
|
||||||
|
echo "處理完成!\n";
|
||||||
|
break;
|
||||||
|
case 'failed':
|
||||||
|
echo "處理失敗: " . ($job['error'] ?? '未知錯誤') . "\n";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 5: 搜尋內容
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
// 搜尋相關片段
|
||||||
|
$results = Momentry_API::search('測試關鍵字', 5);
|
||||||
|
|
||||||
|
foreach ($results['results'] as $result) {
|
||||||
|
echo "UUID: " . $result['chunk_id'] . "\n";
|
||||||
|
echo "分數: " . $result['score'] . "\n";
|
||||||
|
echo "內容: " . ($result['text'] ?? '') . "\n";
|
||||||
|
echo "---\n";
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### WordPress Shortcode 範例
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
// 在 functions.php 中加入
|
||||||
|
add_shortcode('momentry_search', function($atts) {
|
||||||
|
$atts = shortcode_atts([
|
||||||
|
'query' => '',
|
||||||
|
'limit' => 10,
|
||||||
|
], $atts);
|
||||||
|
|
||||||
|
if (empty($atts['query'])) {
|
||||||
|
return '<p>請輸入搜尋關鍵字</p>';
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$results = Momentry_API::search($atts['query'], $atts['limit']);
|
||||||
|
|
||||||
|
if (empty($results['results'])) {
|
||||||
|
return '<p>找不到相關結果</p>';
|
||||||
|
}
|
||||||
|
|
||||||
|
$html = '<div class="momentry-results">';
|
||||||
|
$html .= '<h3>搜尋結果: ' . esc_html($atts['query']) . '</h3>';
|
||||||
|
$html .= '<ul>';
|
||||||
|
|
||||||
|
foreach ($results['results'] as $result) {
|
||||||
|
$html .= '<li>';
|
||||||
|
$html .= '<strong>時間: ' . ($result['start_time'] ?? 'N/A') . 's</strong>';
|
||||||
|
$html .= '<br>';
|
||||||
|
$html .= esc_html($result['text'] ?? '無文字描述');
|
||||||
|
$html .= '</li>';
|
||||||
|
}
|
||||||
|
|
||||||
|
$html .= '</ul></div>';
|
||||||
|
return $html;
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
return '<p>搜尋服務暫時無法使用</p>';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
**使用方式**:
|
||||||
|
```html
|
||||||
|
[momentry_search query="關鍵字" limit="5"]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 完整 n8n Workflow 範例
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────┐
|
||||||
|
│ 觸發 (定時) │
|
||||||
|
└──────┬───────┘
|
||||||
|
▼
|
||||||
|
┌──────────────┐ ┌──────────────┐
|
||||||
|
│ 查詢影片 │────►│ 比對新檔案 │
|
||||||
|
│ /videos │ │ │
|
||||||
|
└──────┬───────┘ └──────────────┘
|
||||||
|
│ │
|
||||||
|
▼ ▼
|
||||||
|
┌──────────────┐ ┌──────────────┐
|
||||||
|
│ 等待處理 │◄────│ 輪詢任務狀態 │
|
||||||
|
│ /jobs/:uuid │ │ /jobs/:uuid │
|
||||||
|
└──────┬───────┘ └──────────────┘
|
||||||
|
│
|
||||||
|
▼ (completed)
|
||||||
|
┌──────────────┐
|
||||||
|
│ 搜尋測試 │
|
||||||
|
│ /search │
|
||||||
|
└──────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**注意**:
|
||||||
|
- 處理時間視影片長度而定(1分鐘影片約需 2-5 分鐘處理)
|
||||||
|
- 大量影片時建議分批上傳
|
||||||
@@ -0,0 +1,281 @@
|
|||||||
|
# Momentry Chunk 資料結構說明
|
||||||
|
|
||||||
|
> **對象**: marcom 團隊
|
||||||
|
> **版本**: V1.0 | **日期**: 2026-03-25
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 什麼是 Chunk?
|
||||||
|
|
||||||
|
Chunk(片段)是影片處理後的最小單位。當影片上傳後,系統會自動:
|
||||||
|
|
||||||
|
1. **分析** - 偵測場景、人臉、姿態
|
||||||
|
2. **轉換** - 語音轉文字(ASR)
|
||||||
|
3. **分段** - 將內容切割成可搜尋的片段
|
||||||
|
4. **向量化** - 產生可搜尋的特徵向量
|
||||||
|
|
||||||
|
每個 Chunk 就是一個**可獨立搜尋的內容單位**。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Chunk 資料結構
|
||||||
|
|
||||||
|
### 2.1 主要欄位
|
||||||
|
|
||||||
|
| 欄位名 | 類型 | 說明 | 範例 |
|
||||||
|
|--------|------|------|------|
|
||||||
|
| `uuid` | 字串 (32) | 影片唯一識別碼 | `952f5854b9febad1` |
|
||||||
|
| `chunk_id` | 字串 (64) | Chunk 唯一識別碼 | `asr_00001` |
|
||||||
|
| `chunk_index` | 整數 | Chunk 順序號碼 | `1` |
|
||||||
|
| `chunk_type` | 字串 (32) | Chunk 類型 | `sentence` |
|
||||||
|
| `start_time` | 浮點數 | 開始時間(秒) | `12.5` |
|
||||||
|
| `end_time` | 浮點數 | 結束時間(秒) | `18.3` |
|
||||||
|
| `content` | JSONB | 詳細內容 | 見下方 |
|
||||||
|
| `vector_id` | 字串 (64) | 向量 ID | `vec_12345` |
|
||||||
|
| `text_content` | 文字 | 純文字內容 | `這是一段話` |
|
||||||
|
| `fps` | 浮點數 | 影片幀率 | `24.0` |
|
||||||
|
| `start_frame` | 整數 | 開始幀數 | `300` |
|
||||||
|
| `end_frame` | 整數 | 結束幀數 | `439` |
|
||||||
|
| `frame_count` | 整數 | 總幀數 | `139` |
|
||||||
|
|
||||||
|
### 2.2 Chunk 類型說明
|
||||||
|
|
||||||
|
| 類型 | ID | 說明 | 來源處理器 |
|
||||||
|
|------|-----|------|-----------|
|
||||||
|
| `sentence` | `sentence` | 語音轉文字片段 | ASR 處理 |
|
||||||
|
| `time` | `time_based` | 固定時間分段 | 系統自動切割 |
|
||||||
|
| `cut` | `cut` | 場景變化片段 | CUT 處理 |
|
||||||
|
| `trace` | `trace` | 軌跡追蹤片段 | YOLO 追蹤處理 |
|
||||||
|
| `story` | `story` | 故事線片段(父子區塊) | Story 分析處理 |
|
||||||
|
|
||||||
|
**父子區塊關係**:
|
||||||
|
- `story` 是**父區塊**,可包含多個 `sentence`、`cut`、`trace` 子區塊
|
||||||
|
- 透過 `parent_chunk_id` 和 `child_chunk_ids` 建立階層關係
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Content JSON 結構
|
||||||
|
|
||||||
|
每個 Chunk 的 `content` 欄位包含詳細的處理結果:
|
||||||
|
|
||||||
|
### 3.1 ASR Chunk(語音轉文字)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"text": "今天天氣非常好,我們去郊外踏青吧",
|
||||||
|
"words": [
|
||||||
|
{
|
||||||
|
"word": "今天",
|
||||||
|
"start": 12.5,
|
||||||
|
"end": 12.8,
|
||||||
|
"confidence": 0.95
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"word": "天氣",
|
||||||
|
"start": 12.8,
|
||||||
|
"end": 13.1,
|
||||||
|
"confidence": 0.92
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"language": "zh-TW",
|
||||||
|
"speaker": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 Cut Chunk(場景偵測)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"scenes": [
|
||||||
|
{
|
||||||
|
"scene_id": "cut_001",
|
||||||
|
"start_time": 12.5,
|
||||||
|
"end_time": 45.2,
|
||||||
|
"transition": "cut",
|
||||||
|
"confidence": 0.98
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 Trace Chunk(軌跡追蹤)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"track_id": "track_001",
|
||||||
|
"object_class": "person",
|
||||||
|
"frames": [
|
||||||
|
{
|
||||||
|
"frame": 300,
|
||||||
|
"bbox": [120, 80, 200, 300],
|
||||||
|
"confidence": 0.95
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"frame": 301,
|
||||||
|
"bbox": [122, 82, 202, 302],
|
||||||
|
"confidence": 0.94
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"total_frames": 180
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.4 Story Chunk(故事線)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"story_id": "story_001",
|
||||||
|
"title": "開場介紹",
|
||||||
|
"summary": "主持人介紹節目主題",
|
||||||
|
"child_chunk_ids": ["sentence_00001", "sentence_00002", "cut_00001"],
|
||||||
|
"tags": ["intro", "host"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.5 Metadata(其他偵測資訊)
|
||||||
|
|
||||||
|
人臉(Face)、文字辨識(OCR)、姿態(Pose)等偵測結果會附加在 `metadata` 欄位:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"faces": [
|
||||||
|
{
|
||||||
|
"bbox": [120, 80, 200, 180],
|
||||||
|
"confidence": 0.87,
|
||||||
|
"emotion": "neutral"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ocr": {
|
||||||
|
"text": "MOMENTRY",
|
||||||
|
"confidence": 0.96
|
||||||
|
},
|
||||||
|
"pose": {
|
||||||
|
"keypoints": [
|
||||||
|
{"name": "nose", "x": 192, "y": 85, "confidence": 0.95}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 時間格式說明
|
||||||
|
|
||||||
|
### 4.1 秒數格式(常用)
|
||||||
|
|
||||||
|
```
|
||||||
|
格式: 秒.幀數
|
||||||
|
範例: 1234.60 = 第 1234 秒 + 第 60 幀
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 時間軸格式
|
||||||
|
|
||||||
|
```
|
||||||
|
格式: HH:MM:SS.FF
|
||||||
|
範例: 00:20:34.12 = 20分34秒12幀
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.3 幀數計算
|
||||||
|
|
||||||
|
```
|
||||||
|
幀數 = 秒數 × fps
|
||||||
|
例如: 12.5秒 × 24fps = 300幀
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 實際資料範例
|
||||||
|
|
||||||
|
假設有一個影片,包含以下處理結果:
|
||||||
|
|
||||||
|
### 5.1 語音片段
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"uuid": "952f5854b9febad1",
|
||||||
|
"chunk_id": "asr_00001",
|
||||||
|
"chunk_type": "sentence",
|
||||||
|
"start_time": 12.5,
|
||||||
|
"end_time": 18.3,
|
||||||
|
"content": {
|
||||||
|
"text": "今天天氣非常好,我們去郊外踏青吧",
|
||||||
|
"language": "zh-TW"
|
||||||
|
},
|
||||||
|
"text_content": "今天天氣非常好,我們去郊外踏青吧",
|
||||||
|
"start_frame": 300,
|
||||||
|
"end_frame": 439,
|
||||||
|
"fps": 24.0
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 場景片段
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"uuid": "952f5854b9febad1",
|
||||||
|
"chunk_id": "cut_00001",
|
||||||
|
"chunk_type": "cut",
|
||||||
|
"start_time": 45.0,
|
||||||
|
"end_time": 120.5,
|
||||||
|
"content": {
|
||||||
|
"scenes": [{
|
||||||
|
"scene_id": "cut_001",
|
||||||
|
"transition": "cut",
|
||||||
|
"confidence": 0.98
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
"start_frame": 1080,
|
||||||
|
"end_frame": 2892,
|
||||||
|
"fps": 24.0
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 如何使用 Chunk
|
||||||
|
|
||||||
|
### 6.1 搜尋相關片段
|
||||||
|
|
||||||
|
當使用者搜尋「天氣」時,系統會:
|
||||||
|
|
||||||
|
1. 將「天氣」轉換為向量
|
||||||
|
2. 在向量資料庫中搜尋相似向量
|
||||||
|
3. 找到相關的 Chunk
|
||||||
|
4. 返回時間軸和內容
|
||||||
|
|
||||||
|
### 6.2 播放指定片段
|
||||||
|
|
||||||
|
取得 Chunk 後可播放:
|
||||||
|
|
||||||
|
```
|
||||||
|
開始時間: 12.5 秒
|
||||||
|
結束時間: 18.3 秒
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.3 組合多個 Chunk
|
||||||
|
|
||||||
|
多個相關 Chunk 可以組合成一個章節或故事線。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 快速參考
|
||||||
|
|
||||||
|
| 項目 | 說明 |
|
||||||
|
|------|------|
|
||||||
|
| UUID | 影片唯一識別碼(16位 hex) |
|
||||||
|
| Chunk ID | 片段識別碼(如 `sentence_00001`) |
|
||||||
|
| chunk_type | 片段類型(sentence/time/cut/trace/story) |
|
||||||
|
| start_time | 開始時間(秒) |
|
||||||
|
| end_time | 結束時間(秒) |
|
||||||
|
| text_content | 純文字內容 |
|
||||||
|
| content | 詳細 JSON 結構 |
|
||||||
|
| metadata | 人臉、OCR、姿態等偵測結果 |
|
||||||
|
| parent_chunk_id | 父區塊 ID(用於 story 區塊) |
|
||||||
|
| child_chunk_ids | 子區塊 ID 列表(story 區塊專用) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**文件版本**: V1.0
|
||||||
|
**最後更新**: 2026-03-25
|
||||||
@@ -82,9 +82,9 @@ def generate_caption_with_llava(
|
|||||||
"""Generate caption using LLaVA model"""
|
"""Generate caption using LLaVA model"""
|
||||||
try:
|
try:
|
||||||
# Try to use transformers with LLaVA
|
# Try to use transformers with LLaVA
|
||||||
from transformers import AutoProcessor, AutoModelForVision2Seq
|
from transformers import AutoProcessor, AutoModelForVision2Seq # noqa: F401
|
||||||
import torch
|
import torch # noqa: F401
|
||||||
from PIL import Image
|
from PIL import Image # noqa: F401
|
||||||
|
|
||||||
# Note: This requires llava-hf/llava-1.5-7b-hf or similar
|
# Note: This requires llava-hf/llava-1.5-7b-hf or similar
|
||||||
# For now, return a placeholder
|
# For now, return a placeholder
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ def generate_parent_child_chunks(
|
|||||||
asr_segments = asr_data.get("segments", [])
|
asr_segments = asr_data.get("segments", [])
|
||||||
cut_scenes = cut_data.get("scenes", [])
|
cut_scenes = cut_data.get("scenes", [])
|
||||||
yolo_frames = yolo_data.get("frames", [])
|
yolo_frames = yolo_data.get("frames", [])
|
||||||
ocr_frames = ocr_data.get("frames", [])
|
_ocr_frames = ocr_data.get("frames", [])
|
||||||
|
|
||||||
# Create child chunks from ASR segments
|
# Create child chunks from ASR segments
|
||||||
asr_child_ids = []
|
asr_child_ids = []
|
||||||
|
|||||||
@@ -148,6 +148,30 @@ struct SearchRequest {
|
|||||||
uuid: Option<String>,
|
uuid: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct CacheToggleRequest {
|
||||||
|
enabled: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
struct CacheToggleResponse {
|
||||||
|
success: bool,
|
||||||
|
cache_enabled: bool,
|
||||||
|
message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct UnregisterRequest {
|
||||||
|
uuid: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
struct UnregisterResponse {
|
||||||
|
success: bool,
|
||||||
|
uuid: String,
|
||||||
|
message: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
struct SearchResult {
|
struct SearchResult {
|
||||||
uuid: String,
|
uuid: String,
|
||||||
@@ -1300,6 +1324,56 @@ async fn get_job(
|
|||||||
Ok(Json(response))
|
Ok(Json(response))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn cache_toggle(
|
||||||
|
State(_state): State<AppState>,
|
||||||
|
Json(req): Json<CacheToggleRequest>,
|
||||||
|
) -> Result<Json<CacheToggleResponse>, StatusCode> {
|
||||||
|
tracing::info!("[cache_toggle] Setting cache enabled to: {}", req.enabled);
|
||||||
|
|
||||||
|
crate::core::config::set_cache_enabled(req.enabled);
|
||||||
|
|
||||||
|
let response = CacheToggleResponse {
|
||||||
|
success: true,
|
||||||
|
cache_enabled: req.enabled,
|
||||||
|
message: if req.enabled {
|
||||||
|
"Cache enabled".to_string()
|
||||||
|
} else {
|
||||||
|
"Cache disabled".to_string()
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
tracing::info!("[cache_toggle] SUCCESS");
|
||||||
|
Ok(Json(response))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn unregister(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
Json(req): Json<UnregisterRequest>,
|
||||||
|
) -> Result<Json<UnregisterResponse>, StatusCode> {
|
||||||
|
tracing::info!("[unregister] Unregistering video: {}", req.uuid);
|
||||||
|
|
||||||
|
let db = &state.api_state.db;
|
||||||
|
|
||||||
|
match db.delete_video(&req.uuid).await {
|
||||||
|
Ok(_) => {
|
||||||
|
tracing::info!("[unregister] SUCCESS - deleted: {}", req.uuid);
|
||||||
|
Ok(Json(UnregisterResponse {
|
||||||
|
success: true,
|
||||||
|
uuid: req.uuid,
|
||||||
|
message: "Video unregistered successfully".to_string(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
tracing::error!("[unregister] ERROR - {}", e);
|
||||||
|
Ok(Json(UnregisterResponse {
|
||||||
|
success: false,
|
||||||
|
uuid: req.uuid,
|
||||||
|
message: format!("Failed to unregister: {}", e),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn start_server(host: &str, port: u16) -> anyhow::Result<()> {
|
pub async fn start_server(host: &str, port: u16) -> anyhow::Result<()> {
|
||||||
let _ = SERVER_START.set(Instant::now());
|
let _ = SERVER_START.set(Instant::now());
|
||||||
|
|
||||||
@@ -1319,6 +1393,7 @@ pub async fn start_server(host: &str, port: u16) -> anyhow::Result<()> {
|
|||||||
|
|
||||||
let protected_routes = Router::new()
|
let protected_routes = Router::new()
|
||||||
.route("/api/v1/register", post(register))
|
.route("/api/v1/register", post(register))
|
||||||
|
.route("/api/v1/unregister", post(unregister))
|
||||||
.route("/api/v1/probe", post(probe))
|
.route("/api/v1/probe", post(probe))
|
||||||
.route("/api/v1/search", post(search))
|
.route("/api/v1/search", post(search))
|
||||||
.route("/api/v1/n8n/search", post(n8n_search))
|
.route("/api/v1/n8n/search", post(n8n_search))
|
||||||
@@ -1328,6 +1403,7 @@ pub async fn start_server(host: &str, port: u16) -> anyhow::Result<()> {
|
|||||||
.route("/api/v1/progress/:uuid", get(get_progress))
|
.route("/api/v1/progress/:uuid", get(get_progress))
|
||||||
.route("/api/v1/jobs", get(list_jobs))
|
.route("/api/v1/jobs", get(list_jobs))
|
||||||
.route("/api/v1/jobs/:uuid", get(get_job))
|
.route("/api/v1/jobs/:uuid", get(get_job))
|
||||||
|
.route("/api/v1/config/cache", post(cache_toggle))
|
||||||
.layer(axum::middleware::from_fn_with_state(
|
.layer(axum::middleware::from_fn_with_state(
|
||||||
state.api_state.clone(),
|
state.api_state.clone(),
|
||||||
api_key_validation,
|
api_key_validation,
|
||||||
|
|||||||
@@ -1,5 +1,23 @@
|
|||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use std::env;
|
use std::env;
|
||||||
|
use std::sync::RwLock;
|
||||||
|
|
||||||
|
pub static RUNTIME_CACHE_ENABLED: Lazy<RwLock<bool>> = Lazy::new(|| {
|
||||||
|
let initial = env::var("MONGODB_CACHE_ENABLED")
|
||||||
|
.unwrap_or_else(|_| "true".to_string())
|
||||||
|
.parse()
|
||||||
|
.unwrap_or(true);
|
||||||
|
RwLock::new(initial)
|
||||||
|
});
|
||||||
|
|
||||||
|
pub fn get_cache_enabled() -> bool {
|
||||||
|
*RUNTIME_CACHE_ENABLED.read().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_cache_enabled(enabled: bool) {
|
||||||
|
*RUNTIME_CACHE_ENABLED.write().unwrap() = enabled;
|
||||||
|
tracing::info!("Cache enabled set to: {}", enabled);
|
||||||
|
}
|
||||||
|
|
||||||
pub static DATABASE_URL: Lazy<String> = Lazy::new(|| {
|
pub static DATABASE_URL: Lazy<String> = Lazy::new(|| {
|
||||||
env::var("DATABASE_URL")
|
env::var("DATABASE_URL")
|
||||||
|
|||||||
@@ -584,6 +584,40 @@ impl PostgresDb {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn delete_video(&self, uuid: &str) -> Result<()> {
|
||||||
|
tracing::info!("[PostgresDb] Deleting video: {}", uuid);
|
||||||
|
|
||||||
|
let tx = self.pool.begin().await?;
|
||||||
|
|
||||||
|
sqlx::query("DELETE FROM chunk_vectors WHERE uuid = $1")
|
||||||
|
.bind(uuid)
|
||||||
|
.execute(&self.pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
sqlx::query("DELETE FROM chunks WHERE uuid = $1")
|
||||||
|
.bind(uuid)
|
||||||
|
.execute(&self.pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
sqlx::query("DELETE FROM processor_results WHERE video_id IN (SELECT id FROM videos WHERE uuid = $1)")
|
||||||
|
.bind(uuid)
|
||||||
|
.execute(&self.pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
sqlx::query("DELETE FROM videos WHERE uuid = $1")
|
||||||
|
.bind(uuid)
|
||||||
|
.execute(&self.pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
tx.commit().await?;
|
||||||
|
|
||||||
|
let mut cache = self.cache.write().await;
|
||||||
|
cache.videos.remove(uuid);
|
||||||
|
|
||||||
|
tracing::info!("[PostgresDb] Video deleted: {}", uuid);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn get_storage_status(&self, uuid: &str) -> Result<Option<StorageStatus>> {
|
pub async fn get_storage_status(&self, uuid: &str) -> Result<Option<StorageStatus>> {
|
||||||
if let Some(video) = self.get_video_by_uuid(uuid).await? {
|
if let Some(video) = self.get_video_by_uuid(uuid).await? {
|
||||||
Ok(Some(video.storage))
|
Ok(Some(video.storage))
|
||||||
|
|||||||
Reference in New Issue
Block a user