# 帳票設計書 4-VapourSynthスクリプト

## 概要

本ドキュメントは、StaxRipにおけるVapourSynthスクリプト出力機能の設計書である。ビデオエンコード用のVapourSynth（Python）フィルタスクリプトを生成し、.vpyファイルとして出力する機能について記述する。

### 本帳票の処理概要

VapourSynthスクリプト機能は、ユーザーが設定したビデオフィルタチェーン（ソースフィルタ、クロップ、リサイズ、デインターレース、ノイズ除去等）をVapourSynth（Python）形式で記述し、.vpyファイルとして出力する機能である。

**業務上の目的・背景**：VapourSynthはAviSynthの後継として開発された高性能フレームサーバーで、Pythonをスクリプト言語として使用する。並列処理性能が高く、モダンなビデオ処理パイプラインを構築できる。StaxRipはGUIからVapourSynthスクリプトを自動生成することで、Pythonの知識がなくても高度なビデオ処理を可能にする。

**帳票の利用シーン**：エンコード処理開始時、プレビュー表示時、クロップ調整時、フィルタ設定変更時。

**主要な出力内容**：
1. Pythonインポート文（os, sys, vapoursynth）
2. VapourSynthコア初期化
3. プラグインロード（core.std.LoadPlugin / core.avs.LoadPlugin）
4. Pythonスクリプトロード（importlib.machinery.SourceFileLoader）
5. ソースフィルタ（ffms2.Source、lsmas.LibavSMASHSource等）
6. 各種フィルタ（core.std.Crop、core.resize.*、havsfunc.QTGMC等）
7. set_output()呼び出し

**帳票の出力タイミング**：VideoScript.Synchronizeメソッド呼び出し時に自動生成される。

**帳票の利用者**：動画編集者、エンコード担当者、VapourSynthスクリプト開発者。

## 帳票種別

スクリプト出力（VapourSynth/Pythonスクリプト）

## 利用画面

| 画面No | 画面名 | URL/ルーティング | 出力操作 |
|--------|--------|-----------------|---------|
| - | MainForm | メインフォーム | フィルタ設定変更時に自動生成 |
| - | CodeEditor | コードエディタ | スクリプト編集・保存時 |
| 2 | PreviewForm | プレビューフォーム | プレビュー表示時 |
| - | CropForm | クロップ調整フォーム | クロップ値変更時 |

## 出力形式

### 基本仕様

| 項目 | 内容 |
|-----|------|
| ファイル形式 | VapourSynthスクリプト (.vpy) |
| 用紙サイズ | - |
| 向き | - |
| ファイル名 | {TargetFile.Base}.vpy |
| 出力方法 | ファイル保存 |
| 文字コード | UTF-8 |

### スクリプト固有設定

| 項目 | 内容 |
|-----|------|
| 改行コード | システム依存（BR定数） |
| インデント | なし（Pythonの文法に従う） |
| コメント形式 | # コメント |

## 帳票レイアウト

### レイアウト概要

VapourSynthスクリプトは、インポート部、プラグインロード部、フィルタチェーン部、出力設定部で構成される。

```
┌─────────────────────────────────────┐
│  import os, sys                     │
│  import vapoursynth as vs           │
│  core = vs.core                     │
├─────────────────────────────────────┤
│  # ポータブルプラグインロード         │
│  core.std.LoadPlugin(...)           │
│  sys.path.append(...)               │
├─────────────────────────────────────┤
│  # Pythonスクリプトロード            │
│  module = importlib.machinery...    │
├─────────────────────────────────────┤
│  # DLLプラグインロード               │
│  core.std.LoadPlugin(...)           │
│  core.avs.LoadPlugin(...)           │
├─────────────────────────────────────┤
│  # CodeAtTop                        │
│  {ユーザー定義コード}               │
├─────────────────────────────────────┤
│  # Filter Chain                     │
│  clip = core.ffms2.Source(...)      │
│  clip = core.std.Crop(...)          │
│  clip = core.resize.Bicubic(...)    │
├─────────────────────────────────────┤
│  # CodeAtBottom                     │
│  {ユーザー定義コード}               │
├─────────────────────────────────────┤
│  clip.set_output()                  │
└─────────────────────────────────────┘
```

### ヘッダー部（インポート・初期化）

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | os/sys インポート | 標準ライブラリインポート | 固定 | import os, sys |
| 2 | vapoursynth インポート | VapourSynthモジュールインポート | 固定 | import vapoursynth as vs |
| 3 | core 初期化 | VapourSynthコア取得 | 固定 | core = vs.core |
| 4 | ポータブルプラグインロード | ポータブルモード時の自動ロード | GetVsPortableAutoLoadPluginCode() | core.std.LoadPlugin(...) |
| 5 | sys.path.append | プラグインスクリプトパス追加 | Folder.Startup | sys.path.append(r"...") |

### 明細部（プラグインロード・フィルタチェーン）

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | Pythonモジュールロード | .pyスクリプトのロード | Package.Items / VsFilterNames | module = importlib.machinery.SourceFileLoader(...) |
| 2 | DLLプラグインロード | DLLプラグインのロード | Package.Items / VsFilterNames | core.std.LoadPlugin(r"...", altsearchpath=True) |
| 3 | AVSプラグインロード | AVS互換プラグインのロード | Package.Items / AvsFilterNames | core.avs.LoadPlugin(r"...") |
| 4 | CodeAtTop | ユーザー定義先頭コード | p.CodeAtTop | そのまま出力 |
| 5 | Source | ソースフィルタ | VideoFilter (Category="Source") | clip = core.ffms2.Source(...) |
| 6 | Crop | クロップフィルタ | VideoFilter (Category="Crop") | clip = core.std.Crop(clip, ...) |
| 7 | Field | デインターレースフィルタ | VideoFilter (Category="Field") | clip = havsfunc.QTGMC(clip, ...) |
| 8 | Noise | ノイズ除去フィルタ | VideoFilter (Category="Noise") | clip = core.dfttest.DFTTest(clip, ...) |
| 9 | Resize | リサイズフィルタ | VideoFilter (Category="Resize") | clip = core.resize.Bicubic(clip, ...) |
| 10 | CodeAtBottom | ユーザー定義末尾コード | p.CodeAtBottom | そのまま出力 |

### フッター部（出力設定）

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | set_output | 出力クリップ設定 | 自動追加（未定義時） | clip.set_output() |

## 出力条件

### 抽出条件

| 条件名 | 説明 | 必須 |
|-------|------|-----|
| パス有効 | Path != "" | Yes |
| ソースフィルタ存在 | Filters(0).Category = "Source" | Yes |
| フィルタ有効 | filter.Active = True | Yes |

### ソート順

| 優先度 | 項目 | 昇順/降順 |
|-------|------|---------|
| 1 | フィルタ定義順 | 昇順（リスト順） |

### 改ページ条件

改ページなし（連続スクリプト）

## データベース参照仕様

### 参照テーブル一覧

本機能はデータベースを使用しない。

| テーブル名 | 用途 | 結合条件 |
|-----------|------|---------|
| - | - | - |

### 外部データソース

| ソース | 用途 | 取得方法 |
|--------|------|---------|
| Package.Items | プラグイン情報 | Package.Items.Values.OfType(Of PluginPackage) |
| FilterCategory | デフォルトフィルタ定義 | FilterCategory.GetVapourSynthDefaults() |
| Project | プロジェクト設定 | p（グローバル変数） |

## 計算仕様

### 計算項目一覧

| 項目名 | 計算式 | 端数処理 | 備考 |
|-------|-------|---------|------|
| - | - | - | スクリプト生成時の計算なし |

## 処理フロー

### 出力フロー

```mermaid
flowchart TD
    A[Synchronize呼び出し] --> B{Path有効?}
    B -->|No| Z[処理終了]
    B -->|Yes| C[GetScript呼び出し]
    C --> D[フィルタチェーン構築]
    D --> E[ModifyVSScript呼び出し]
    E --> F[FindFunctionsVS呼び出し]
    F --> G[プラグインロードコード生成]
    G --> H[インポート文追加]
    H --> I{set_output存在?}
    I -->|No| J[set_output追加]
    I -->|Yes| K[スクリプト結合]
    J --> K
    K --> L[WriteFileUTF8出力]
    L --> M[フレームサーバー起動]
    M --> N[Info取得]
    N --> O[終了]
```

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 表示メッセージ | 対処方法 |
|----------|---------|--------------|---------|
| ソースフィルタ不正 | Filters(0).Category != "Source" | "The first filter must be a source filter." | デフォルトフィルタにリセット |
| パッケージ未検証 | Package.VapourSynth.VerifyOK = False | VerifyOKダイアログ表示 | AbortException |
| vspipe未検証 | Package.vspipe.VerifyOK = False | VerifyOKダイアログ表示 | AbortException |
| スクリプトエラー | フレームサーバー起動失敗 | server.Error に格納 | Error プロパティに設定 |
| プラグイン要件未充足 | plugin.RequirementsFulfilled = False | コメントアウトされたロード文 | 警告コメント追加 |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定データ件数 | 数行〜数百行 |
| 目標出力時間 | 瞬時 |
| 同時出力数上限 | 1 |

## セキュリティ考慮事項

- ファイルパスがスクリプトに含まれるため、パス情報が露出する
- LoadPlugin/SourceFileLoaderにより任意のDLL/Pythonスクリプトが実行される可能性
- Macro.Expandによるマクロ展開が行われる

## 備考

- VapourSynthスクリプトは常にUTF-8で出力される（AviSynthと異なる）
- set_output()が存在しない場合は自動的に追加される
- RGB変換時はmatrix情報（Rec709/Rec601/BT2020）を自動判定
- ポータブルモードの場合、Settings/Plugins配下のプラグインが自動ロードされる
- プラグインの依存関係はスクリプト内でインポート順序を調整して解決

---

## コードリーディングガイド

本帳票を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

VideoScript/VideoFilterクラスの構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | VideoScript.vb | `Source/Video/VideoScript.vb` | Line 12-26でVideoScriptクラスの基本構造を確認 |
| 1-2 | VideoScript.vb | `Source/Video/VideoScript.vb` | Line 39-43でIsVapourSynthプロパティを確認 |
| 1-3 | VideoScript.vb | `Source/Video/VideoScript.vb` | Line 1230-1233でScriptEngine列挙型を確認 |

**読解のコツ**: VideoScriptはProfileを継承し、Engine=ScriptEngine.VapourSynthの場合にVapourSynthスクリプトが生成される。

#### Step 2: エントリーポイントを理解する

スクリプト生成の起点を特定する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | VideoScript.vb | `Source/Video/VideoScript.vb` | Synchronizeメソッド（Line 194-319）がスクリプト同期の入口 |

**主要処理フロー**:
1. **Line 199**: Path空チェック
2. **Line 204**: GetScript()でフィルタスクリプト取得
3. **Line 286-295**: ファイル書き込み（VapourSynthはLine 292でUTF-8）
4. **Line 305-313**: フレームサーバー起動とInfo取得

#### Step 3: VapourSynthスクリプト修正処理を理解する

VapourSynth固有のスクリプト修正処理を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | VideoScript.vb | `Source/Video/VideoScript.vb` | ModifyVSScript（Line 346-372）でスクリプト修正を確認 |

**主要処理フロー**:
- **Line 352-358**: インポート文追加（import os, sys / import vapoursynth as vs / core = vs.core）
- **Line 356**: GetVsPortableAutoLoadPluginCode()でポータブルプラグインロード
- **Line 357**: sys.path.append()でプラグインスクリプトパス追加
- **Line 360-362**: importlib.machinery インポート追加（必要時）
- **Line 367-369**: set_output() 自動追加

#### Step 4: プラグインロード生成処理を理解する

VapourSynth用プラグインロードコード生成を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | VideoScript.vb | `Source/Video/VideoScript.vb` | ModifyVSScript（Line 374-400）でプラグイン解析を確認 |
| 4-2 | VideoScript.vb | `Source/Video/VideoScript.vb` | WriteVSCode（Line 402-431）でロードコード生成を確認 |

**主要処理フロー**:
- **Line 375**: FindFunctionsVS()でスクリプトから関数名抽出
- **Line 376-399**: 各プラグインパッケージをチェック
- **Line 403-417**: Pythonスクリプト（.py）のSourceFileLoaderロード
- **Line 418-430**: DLLプラグインのcore.std.LoadPlugin/core.avs.LoadPluginロード
- **Line 424-425**: 要件未充足時のコメントアウト処理

#### Step 5: 関数名抽出処理を理解する

VapourSynthスクリプトからの関数名抽出を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | VideoScript.vb | `Source/Video/VideoScript.vb` | FindFunctionsVS（Line 578-610）で関数名抽出を確認 |

**主要処理フロー**:
- **Line 581-607**: ドット区切りで関数名を抽出
- **Line 601**: コメント内の関数をスキップ（IsFunctionCommented）

#### Step 6: RGB変換処理を理解する

convertToRGB=True時の色空間変換処理を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 6-1 | VideoScript.vb | `Source/Video/VideoScript.vb` | Line 217-284でVapourSynth用RGB変換コードを確認 |

**主要処理フロー**:
- **Line 229-276**: matrix/transfer/primaries の自動判定コード
- **Line 277**: resize.Bicubicでの色変換
- **Line 278-281**: libp2p.Pack()でのパッキング

### プログラム呼び出し階層図

```
VideoScript.Synchronize()
    │
    ├─ GetScript()
    │      ├─ p.CodeAtTop 追加
    │      ├─ Filters[].Script 追加（Active=Trueのみ）
    │      └─ p.CodeAtBottom 追加
    │
    ├─ ModifyVSScript()
    │      │
    │      ├─ GetVsPortableAutoLoadPluginCode() ← ポータブルプラグイン
    │      │
    │      ├─ ModifyVSScript(script, code) [オーバーロード]
    │      │      │
    │      │      ├─ FindFunctionsVS() ← スクリプトから関数名抽出
    │      │      │
    │      │      └─ Package.Items.OfType(Of PluginPackage)
    │      │             │
    │      │             └─ WriteVSCode()
    │      │                    ├─ SourceFileLoader(...) ← .pyスクリプト
    │      │                    └─ core.std.LoadPlugin(...) ← .dll
    │      │
    │      └─ set_output() 追加（未定義時）
    │
    ├─ WriteFileUTF8() ← UTF-8エンコーディング
    │
    └─ FrameServerFactory.Create()
           └─ server.Info / server.Error 取得
```

### データフロー図

```
[入力]                    [処理]                         [出力]

p.CodeAtTop ─────────▶ GetScript() ─────────────▶ スクリプト本体
     │                       │
Filters[] ───────────────────┘
     │
p.CodeAtBottom ─────────────┘
                              │
                              ▼
Package.Items ──────────▶ ModifyVSScript() ──────▶ プラグインロード
                              │                    + インポート文
                              │
                              ▼
                         set_output() 追加
                              │
                              ▼
                         WriteFileUTF8() ─────────▶ .vpy ファイル
                              │                    (UTF-8)
                              ▼
                         FrameServer起動 ─────────▶ Info/Error
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| VideoScript.vb | `Source/Video/VideoScript.vb` | ソース | スクリプト生成メインロジック |
| FrameServer.vb | `Source/Video/FrameServer.vb` | ソース | フレームサーバー基底クラス |
| Package.vb | （推定） | ソース | プラグインパッケージ管理 |
| CodeEditor.vb | `Source/Forms/CodeEditor.vb` | ソース | スクリプトエディタUI |
| VapourSynthFilterProfileDefaults.txt | 埋め込みリソース | リソース | デフォルトフィルタ定義 |
| Project.vb | `Source/General/Project.vb` | ソース | Scriptプロパティ管理 |
| libp2p | 外部プラグイン | プラグイン | RGB パッキング処理 |
