Skip to content

Commit 973a056

Browse files
committed
Add API plans for video generation, preview, thumbnail, progress tracking, queue system, and continuous playback
- Create api_plan.md detailing the purpose, framework, code structure, and endpoints for the API. - Establish api_preview_plan.md to implement a preview feature during video generation using SSE. - Outline api_thumbnail_plan.md to return input image thumbnails alongside video download URLs in the /result endpoint. - Document progress_sse_plan.md for real-time job progress updates using Server-Sent Events (SSE) in the React frontend. - Explain the queue system in queue_explanation.md, detailing components and their roles in job management. - Implement video continuous playback functionality in video continuous_playback_plan.md, utilizing SSE to notify clients of new video files.
1 parent c623ee4 commit 973a056

6 files changed

Lines changed: 217 additions & 0 deletions

File tree

File renamed without changes.

plan/api_preview_plan.md

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
# API プレビュー機能 実装計画
2+
3+
## 1. 目的
4+
5+
`api/` ディレクトリ内の FastAPI アプリケーションにおいて、`demo_gradio.py` と同様の動画生成中のプレビュー機能を実装する。これにより、API クライアントは生成プロセスの途中経過をリアルタイムで確認できるようになる。
6+
7+
## 2. 現状分析と課題
8+
9+
* **`demo_gradio.py` のプレビュー:**
10+
* `worker` 内の `sample_hunyuan` コールバックで `vae_decode_fake` を使用し、プレビュー画像を生成。
11+
* `AsyncStream` を介して Gradio UI にプレビュー画像を送信。
12+
* **`api/worker.py` の現状:**
13+
* `sample_hunyuan` のコールバックは進捗テキスト更新とキャンセルチェックのみ。プレビュー生成・送信は未実装。
14+
* `worker` からクライアントへのリアルタイムデータ送信手段が直接はない。
15+
* **API での実現課題:**
16+
* `api/worker.py` のコールバックでプレビュー画像を生成する必要がある。
17+
* 生成したプレビュー画像を API クライアントにリアルタイムで送信する仕組みが必要。
18+
* `vae_decode_fake` 関数の利用可否確認 (→ 確認済み、利用可能)。
19+
20+
## 3. 計画
21+
22+
### 3.1. 情報収集 (完了)
23+
24+
* `diffusers_helper/hunyuan.py` を確認し、`vae_decode_fake` 関数が存在することを確認した。
25+
26+
### 3.2. API 設計
27+
28+
* 既存の Server-Sent Events (SSE) エンドポイント `/stream/status/{job_id}` (`api.py` 内) を拡張し、プレビュー画像データも送信するようにする。
29+
* SSE イベントのデータ構造に、オプションとして Base64 エンコードされたプレビュー画像 (`preview_image_base64`) を追加する。
30+
31+
```json
32+
{
33+
"job_id": "...",
34+
"status": "processing",
35+
"progress": 25.5,
36+
"progress_step": 5,
37+
"progress_total": 20,
38+
"progress_info": "Sampling...",
39+
"preview_image_base64": "data:image/jpeg;base64,..." // Optional
40+
}
41+
```
42+
43+
### 3.3. 実装方針
44+
45+
* **`api/worker.py` の修正:**
46+
* `callback` 関数内で、`sample_hunyuan` から渡される中間潜在変数 (`d['denoised']`) を取得する。
47+
* `vae_decode_fake` を使用してプレビュー画像を生成する。
48+
* 生成した画像を JPEG 形式にエンコードし、Base64 文字列に変換する (`data:image/jpeg;base64,...` 形式)。
49+
* 変換した Base64 文字列を `queue_manager` の新しい関数 (例: `update_current_preview`) を呼び出してメモリ上のストアに一時保存する。
50+
* **`api/queue_manager.py` の修正:**
51+
* プレビュー情報 (Base64 文字列) を一時的に保持するためのグローバルな辞書 (例: `current_previews = {}`) を追加する。
52+
* `worker.py` からプレビュー情報を受け取り、`current_previews` を更新する関数 (例: `update_current_preview(job_id, preview_base64)`) を追加する。
53+
* SSE ハンドラからプレビュー情報を取得する関数 (例: `get_current_preview(job_id)`) を追加する。
54+
* ジョブ完了時または失敗時に `current_previews` から該当ジョブのエントリを削除する処理を追加する (例: `clear_current_preview(job_id)`)。
55+
* **注意:** このプレビュー情報は揮発性であり、JSON キューファイル (`job_queue.json`) には保存しない。
56+
* **`api/api.py` の修正:**
57+
* `/stream/status/{job_id}` の SSE `event_generator` 関数を修正する。
58+
* ジョブが `processing` 状態の場合、`queue_manager` の新しい関数 (例: `get_current_preview`) を呼び出して最新のプレビュー画像 Base64 文字列を取得する。
59+
* 取得した Base64 文字列を SSE イベントデータの `preview_image_base64` フィールドに含めてクライアントに送信する。
60+
61+
### 3.4. 処理フロー (Mermaid図)
62+
63+
```mermaid
64+
sequenceDiagram
65+
participant Client
66+
participant FastAPI (api.py)
67+
participant QueueManager (queue_manager.py)
68+
participant Worker (worker.py)
69+
participant Models (models.py / diffusers_helper)
70+
71+
Client->>FastAPI: POST /generate (画像, プロンプト)
72+
FastAPI->>QueueManager: add_to_queue()
73+
QueueManager-->>FastAPI: job_id
74+
FastAPI-->>Client: {job_id: ...}
75+
76+
Client->>FastAPI: GET /stream/status/{job_id} (SSE接続)
77+
FastAPI->>FastAPI: event_generator() 開始
78+
79+
loop Worker Thread
80+
Worker->>QueueManager: get_next_job()
81+
QueueManager-->>Worker: job (or None)
82+
opt job is not None
83+
Worker->>QueueManager: update_job_status(job_id, "processing") # ファイル更新
84+
Worker->>Models: モデルロード/準備 ...
85+
Worker->>Models: sample_hunyuan(..., callback=callback_func)
86+
loop Sampling Steps
87+
Models->>Worker: callback_func(d) 呼び出し
88+
Worker->>Models: vae_decode_fake(d['denoised']) # プレビュー生成
89+
Models-->>Worker: preview_image_tensor
90+
Worker->>Worker: 画像をJPEG Base64に変換
91+
Worker->>QueueManager: update_current_preview(job_id, preview_base64) # メモリ更新
92+
Worker->>QueueManager: update_job_progress(...) # ファイル更新 (進捗のみ)
93+
end
94+
Models-->>Worker: generated_latents
95+
Worker->>Models: vae_decode() # 最終デコード
96+
Models-->>Worker: final_pixels
97+
Worker->>Worker: save_bcthw_as_mp4()
98+
Worker->>QueueManager: update_job_status(job_id, "completed") # ファイル更新
99+
Worker->>QueueManager: clear_current_preview(job_id) # メモリクリア
100+
end
101+
end
102+
103+
loop SSE Connection (event_generator)
104+
FastAPI->>QueueManager: get_job_by_id(job_id) (ファイルから進捗取得)
105+
alt job is processing
106+
FastAPI->>QueueManager: get_current_preview(job_id) (メモリからプレビュー取得)
107+
QueueManager-->>FastAPI: preview_base64 (or None)
108+
end
109+
FastAPI->>Client: event: progress, data: {..., preview_image_base64: ...} # 進捗とプレビュー送信
110+
alt job is terminal
111+
FastAPI->>Client: event: status, data: {...} # 最終ステータス送信
112+
break
113+
end
114+
FastAPI->>FastAPI: asyncio.sleep(1)
115+
end
116+
```
117+
118+
## 4. 懸念点
119+
120+
* **データ受け渡し:** `worker` スレッドと SSE ハンドラ (FastAPI の非同期コンテキスト) 間でのプレビューデータ受け渡し (`queue_manager` のメモリ上の辞書) が、スレッドセーフティやパフォーマンスの観点から問題ないか。高頻度更新時の競合やメモリ使用量に注意が必要。
121+
* **パフォーマンス:** プレビュー画像の生成 (`vae_decode_fake`)、JPEG エンコード、Base64 エンコードが `worker` のコールバック内で実行されるため、全体の生成時間に影響を与える可能性がある。
122+
123+
## 5. 次のステップ
124+
125+
* この計画に基づき、`code` モードに切り替えて実装を開始する。
File renamed without changes.
File renamed without changes.
File renamed without changes.
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# 動画連続再生機能 実装計画
2+
3+
## 概要
4+
5+
ローカルの特定ディレクトリ (`outputs`) に順次追加される動画ファイル (`.mp4`) を検知し、FastAPIのSSE (Server-Sent Events) を通じてクライアント (React想定) に通知する。クライアントは通知されたファイル名を元に動画をリクエストし、連続再生を行う。既存のFramePack API (`api/api.py`) に機能を追加し、他の機能への影響を最小限に抑える。
6+
7+
## 計画詳細
8+
9+
1. **設定更新 (`api/settings.py`):**
10+
* 監視対象ディレクトリパス `VIDEO_DIR` を定義する。デフォルトはプロジェクトルート下の `outputs` ディレクトリとする。環境変数 `VIDEO_DIR` が設定されていれば、その値を優先する。
11+
* 動画ファイル配信用エンドポイントのベースURL `VIDEO_BASE_URL``/videos/` として定義する (主にクライアント側の参考情報)。
12+
13+
2. **ファイル監視ロジック (`api/video_watcher.py` - 新規ファイル):**
14+
* `watchdog` ライブラリを使用する `VideoHandler` クラスを作成する。
15+
* `on_created` イベントハンドラを実装し、`.mp4` ファイルが作成された場合のみ、FastAPI側のSSEクライアントキューリスト (`sse_clients`) にファイル名を追加する。
16+
* 監視を開始/停止する関数 (`start_watcher`, `stop_watcher`) を作成する。
17+
* `start_watcher(path, clients)`: 指定されたパスを監視し、通知先のクライアントキューリストを受け取る。`watchdog.observers.Observer` インスタンスを初期化・開始し、そのインスタンスを返す。
18+
* `stop_watcher(observer)`: 受け取った `Observer` インスタンスを停止・結合する。
19+
20+
3. **FastAPIエンドポイント追加 (`api/api.py`):**
21+
* **グローバル変数:**
22+
* `sse_clients = []`: SSEクライアントごとの通知キュー (`asyncio.Queue` など) を保持するリスト。
23+
* `observer = None`: `watchdog``Observer` インスタンスを保持する変数。
24+
* **`/video_stream` (GET, SSE):**
25+
* 新しいクライアント接続時に、専用の通知キューを作成し `sse_clients` に追加する。
26+
* 非同期ジェネレータ関数を定義する。
27+
* 無限ループでクライアントの接続状態をチェックする。
28+
* キューから新しいファイル名を取得し、`data: {filename}\n\n` 形式で `yield` する。
29+
* クライアント切断時には、対応するキューを `sse_clients` から削除し、ループを終了する。
30+
* `StreamingResponse` で上記ジェネレータを返す (`media_type="text/event-stream"`)。
31+
* **`/videos/{filename}` (GET):**
32+
* `settings.VIDEO_DIR` とリクエストされた `filename` を結合して、動画ファイルのフルパスを構築する。
33+
* `os.path.exists` でファイルの存在を確認する。
34+
* 存在すれば `FileResponse` を使用して動画ファイル (`media_type="video/mp4"`) を返す。
35+
* 存在しなければ `HTTPException(status_code=404, detail="File not found")` を発生させる。
36+
* **`/videos` (GET):**
37+
* `settings.VIDEO_DIR` 内のファイルを `os.listdir` で取得する。
38+
* ファイル名が `.mp4` で終わるもののみをフィルタリングする。
39+
* フィルタリングされたファイル名のリストをJSON形式で返す。
40+
41+
4. **ライフサイクル管理 (`api/api.py``lifespan`):**
42+
* 既存の `lifespan` コンテキストマネージャを修正する。
43+
* **Startup:**
44+
* `video_watcher.start_watcher(settings.VIDEO_DIR, sse_clients)` を呼び出し、返された `Observer` インスタンスをグローバル変数 `observer` に格納する。
45+
* **Shutdown:**
46+
* グローバル変数 `observer``None` でなければ、`video_watcher.stop_watcher(observer)` を呼び出してファイル監視プロセスを安全に停止する。
47+
48+
5. **依存関係:**
49+
* `watchdog` ライブラリが必要となるため、プロジェクトの依存関係ファイル (`requirements.txt``pyproject.toml` など) に `watchdog` を追加する。
50+
51+
## Mermaid図
52+
53+
```mermaid
54+
graph TD
55+
subgraph FastAPI Backend (api/api.py)
56+
A[Client connects to /video_stream] --> B{Create SSE queue (e.g., asyncio.Queue)};
57+
B --> C[Add queue to global sse_clients list];
58+
C --> D[Start SSE generation loop (async def)];
59+
D -- New filename in queue --> E[yield f"data: {filename}\n\n"];
60+
D -- Client disconnects --> F[Remove queue from sse_clients & break loop];
61+
62+
G[Client requests /videos/{filename}] --> H{Build file path using settings.VIDEO_DIR};
63+
H -- os.path.exists is True --> I[Return FileResponse(path, media_type="video/mp4")];
64+
H -- os.path.exists is False --> J[Raise HTTPException(404)];
65+
66+
K[Client requests /videos] --> L{os.listdir(settings.VIDEO_DIR)};
67+
L --> M[Filter for .mp4 files, return JSON list];
68+
69+
N[lifespan startup] --> O[observer = video_watcher.start_watcher(VIDEO_DIR, sse_clients)];
70+
P[lifespan shutdown] --> Q[if observer: video_watcher.stop_watcher(observer)];
71+
end
72+
73+
subgraph File System Watcher (api/video_watcher.py - New File)
74+
R[Watchdog Observer monitors VIDEO_DIR] -- New .mp4 created --> S[VideoHandler.on_created];
75+
S --> T{Get filename};
76+
T --> U[Add filename to all queues in sse_clients list];
77+
V[start_watcher(path, clients)] --> W[Initialize Observer & Handler, observer.start(), return observer];
78+
X[stop_watcher(observer)] --> Y[observer.stop(), observer.join()];
79+
end
80+
81+
subgraph React Frontend (Out of scope)
82+
Z[Page load requests /videos] --> AA[Get initial file list];
83+
AA --> BB[Initialize playlist];
84+
CC[Connects to /video_stream] --> DD[Receive filename via SSE];
85+
DD --> EE[Add filename to playlist];
86+
BB & EE --> FF[Select random video from playlist];
87+
FF --> GG[Request /videos/{filename}];
88+
GG --> HH[Receive video data & play];
89+
end
90+
91+
FastAPI_Backend -- Manages --> File_System_Watcher;
92+
```

0 commit comments

Comments
 (0)